diff options
author | Michael Biebl <biebl@debian.org> | 2014-07-06 02:16:10 +0200 |
---|---|---|
committer | Michael Biebl <biebl@debian.org> | 2014-07-06 02:16:10 +0200 |
commit | 33491bc4279481db8ae47213e34a6d695a0e8830 (patch) | |
tree | 097d2b0fdff3fae6885381ae5e57a182cd8cbbba /src/devices | |
parent | 59c3714a494c3b3765657c0551ad82842d98a7d2 (diff) |
Imported Upstream version 0.9.10.0upstream/0.9.10.0
Diffstat (limited to 'src/devices')
111 files changed, 45058 insertions, 0 deletions
diff --git a/src/devices/adsl/Makefile.am b/src/devices/adsl/Makefile.am new file mode 100644 index 000000000..0430f47d0 --- /dev/null +++ b/src/devices/adsl/Makefile.am @@ -0,0 +1,63 @@ +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_srcdir}/src/ppp-manager \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-adsl"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +GLIB_GENERATED = nm-adsl-enum-types.h nm-adsl-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_adsl_enum_types_sources = $(srcdir)/nm-device-adsl.h + +nm-device-adsl-glue.h: $(top_srcdir)/introspection/nm-device-adsl.xml + dbus-binding-tool --prefix=nm_device_adsl --mode=glib-server --output=$@ $< + +BUILT_SOURCES = $(GLIB_GENERATED) nm-device-adsl-glue.h + +pkglib_LTLIBRARIES = libnm-device-plugin-adsl.la + +SYMBOL_VIS_FILE=$(srcdir)/exports.ver + +libnm_device_plugin_adsl_la_SOURCES = \ + nm-atm-manager.c \ + nm-atm-manager.h \ + nm-device-adsl.c \ + nm-device-adsl.h \ + \ + $(BUILT_SOURCES) + +libnm_device_plugin_adsl_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_adsl_la_LIBADD = \ + $(DBUS_LIBS) \ + $(GUDEV_LIBS) + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) + +if ENABLE_TESTS + +check-local: + $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-adsl.so $(SYMBOL_VIS_FILE) + +endif + diff --git a/src/devices/adsl/Makefile.in b/src/devices/adsl/Makefile.in new file mode 100644 index 000000000..708a45d2c --- /dev/null +++ b/src/devices/adsl/Makefile.in @@ -0,0 +1,847 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/devices/adsl +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/build-aux/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \ + $(top_srcdir)/m4/compiler_warnings.m4 \ + $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gnome-code-coverage.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkglibdir)" +LTLIBRARIES = $(pkglib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libnm_device_plugin_adsl_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am__objects_1 = nm-adsl-enum-types.lo +am__objects_2 = $(am__objects_1) +am_libnm_device_plugin_adsl_la_OBJECTS = nm-atm-manager.lo \ + nm-device-adsl.lo $(am__objects_2) +libnm_device_plugin_adsl_la_OBJECTS = \ + $(am_libnm_device_plugin_adsl_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libnm_device_plugin_adsl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libnm_device_plugin_adsl_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libnm_device_plugin_adsl_la_SOURCES) +DIST_SOURCES = $(libnm_device_plugin_adsl_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CKDB_PATH = @CKDB_PATH@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@ +DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SYS_DIR = @DBUS_SYS_DIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHCLIENT_PATH = @DHCLIENT_PATH@ +DHCPCD_PATH = @DHCPCD_PATH@ +DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@ +DLLTOOL = @DLLTOOL@ +DNSMASQ_PATH = @DNSMASQ_PATH@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MAKEFILE = @GLIB_MAKEFILE@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +IPTABLES_PATH = @IPTABLES_PATH@ +IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@ +IWMX_SDK_LIBS = @IWMX_SDK_LIBS@ +KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDL = @LIBDL@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBNDP_CFLAGS = @LIBNDP_CFLAGS@ +LIBNDP_LIBS = @LIBNDP_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@ +LIBSOUP_LIBS = @LIBSOUP_LIBS@ +LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@ +LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MM_GLIB_CFLAGS = @MM_GLIB_CFLAGS@ +MM_GLIB_LIBS = @MM_GLIB_LIBS@ +MOC = @MOC@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEWT_CFLAGS = @NEWT_CFLAGS@ +NEWT_LIBS = @NEWT_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NM_MAJOR_VERSION = @NM_MAJOR_VERSION@ +NM_MICRO_VERSION = @NM_MICRO_VERSION@ +NM_MINOR_VERSION = @NM_MINOR_VERSION@ +NM_MODIFY_SYSTEM_POLICY = @NM_MODIFY_SYSTEM_POLICY@ +NM_VERSION = @NM_VERSION@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POLKIT_CFLAGS = @POLKIT_CFLAGS@ +POLKIT_LIBS = @POLKIT_LIBS@ +POSUB = @POSUB@ +PPPD_PATH = @PPPD_PATH@ +PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@ +PPPOE_PATH = @PPPOE_PATH@ +QT_CFLAGS = @QT_CFLAGS@ +QT_LIBS = @QT_LIBS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@ +SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@ +SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@ +SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +SYSTEM_CA_PATH = @SYSTEM_CA_PATH@ +UDEV_BASE_DIR = @UDEV_BASE_DIR@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND_RULES = @VALGRIND_RULES@ +VAPIGEN = @VAPIGEN@ +VAPIGEN_MAKEFILE = @VAPIGEN_MAKEFILE@ +VAPIGEN_VAPIDIR = @VAPIGEN_VAPIDIR@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nmbinary = @nmbinary@ +nmconfdir = @nmconfdir@ +nmdatadir = @nmdatadir@ +nmrundir = @nmrundir@ +nmstatedir = @nmstatedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +with_dhclient = @with_dhclient@ +with_dhcpcd = @with_dhcpcd@ +with_netconfig = @with_netconfig@ +with_resolvconf = @with_resolvconf@ +with_valgrind = @with_valgrind@ +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_srcdir}/src/ppp-manager \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-adsl"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +GLIB_GENERATED = nm-adsl-enum-types.h nm-adsl-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_adsl_enum_types_sources = $(srcdir)/nm-device-adsl.h +BUILT_SOURCES = $(GLIB_GENERATED) nm-device-adsl-glue.h +pkglib_LTLIBRARIES = libnm-device-plugin-adsl.la +SYMBOL_VIS_FILE = $(srcdir)/exports.ver +libnm_device_plugin_adsl_la_SOURCES = \ + nm-atm-manager.c \ + nm-atm-manager.h \ + nm-device-adsl.c \ + nm-device-adsl.h \ + \ + $(BUILT_SOURCES) + +libnm_device_plugin_adsl_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_adsl_la_LIBADD = \ + $(DBUS_LIBS) \ + $(GUDEV_LIBS) + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/devices/adsl/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/devices/adsl/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ + } + +uninstall-pkglibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ + done + +clean-pkglibLTLIBRARIES: + -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) + @list='$(pkglib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libnm-device-plugin-adsl.la: $(libnm_device_plugin_adsl_la_OBJECTS) $(libnm_device_plugin_adsl_la_DEPENDENCIES) $(EXTRA_libnm_device_plugin_adsl_la_DEPENDENCIES) + $(AM_V_CCLD)$(libnm_device_plugin_adsl_la_LINK) -rpath $(pkglibdir) $(libnm_device_plugin_adsl_la_OBJECTS) $(libnm_device_plugin_adsl_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-adsl-enum-types.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-atm-manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-device-adsl.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +@ENABLE_TESTS_FALSE@check-local: +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(pkglibdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-pkglibLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkglibLTLIBRARIES + +.MAKE: all check check-am install install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am check-local clean \ + clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkglibLTLIBRARIES install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkglibLTLIBRARIES + +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +nm-device-adsl-glue.h: $(top_srcdir)/introspection/nm-device-adsl.xml + dbus-binding-tool --prefix=nm_device_adsl --mode=glib-server --output=$@ $< + +@ENABLE_TESTS_TRUE@check-local: +@ENABLE_TESTS_TRUE@ $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-adsl.so $(SYMBOL_VIS_FILE) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/devices/adsl/exports.ver b/src/devices/adsl/exports.ver new file mode 100644 index 000000000..d2c451244 --- /dev/null +++ b/src/devices/adsl/exports.ver @@ -0,0 +1,7 @@ +{ +global: + nm_device_factory_create; + nm_device_factory_get_device_type; +local: + *; +}; diff --git a/src/devices/adsl/nm-adsl-enum-types.c b/src/devices/adsl/nm-adsl-enum-types.c new file mode 100644 index 000000000..085653317 --- /dev/null +++ b/src/devices/adsl/nm-adsl-enum-types.c @@ -0,0 +1,12 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#include "nm-adsl-enum-types.h" + +#include "nm-device-adsl.h" + + + + diff --git a/src/devices/adsl/nm-adsl-enum-types.h b/src/devices/adsl/nm-adsl-enum-types.h new file mode 100644 index 000000000..88e1fe840 --- /dev/null +++ b/src/devices/adsl/nm-adsl-enum-types.h @@ -0,0 +1,17 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#ifndef __NM_ADSL_ENUM_TYPES_H__ +#define __NM_ADSL_ENUM_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS +G_END_DECLS + +#endif /* __NM_ADSL_ENUM_TYPES_H__ */ + + + diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c new file mode 100644 index 000000000..e8db3596d --- /dev/null +++ b/src/devices/adsl/nm-atm-manager.c @@ -0,0 +1,266 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2013 Red Hat, Inc. + */ + +#include <config.h> + +#include <string.h> +#include <gudev/gudev.h> +#include <gmodule.h> + +#include "nm-atm-manager.h" +#include "nm-device-adsl.h" +#include "nm-device-factory.h" +#include "nm-logging.h" + +typedef struct { + GUdevClient *client; + GSList *devices; + guint start_id; +} NMAtmManagerPrivate; + +#define NM_ATM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_ATM_MANAGER, NMAtmManagerPrivate)) + +static GType nm_atm_manager_get_type (void); + +static void device_factory_interface_init (NMDeviceFactory *factory_iface); + +G_DEFINE_TYPE_EXTENDED (NMAtmManager, nm_atm_manager, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_DEVICE_FACTORY, device_factory_interface_init)) + +/**************************************************************************/ + +#define PLUGIN_TYPE NM_DEVICE_TYPE_ADSL + +G_MODULE_EXPORT NMDeviceFactory * +nm_device_factory_create (GError **error) +{ + return (NMDeviceFactory *) g_object_new (NM_TYPE_ATM_MANAGER, NULL); +} + +G_MODULE_EXPORT NMDeviceType +nm_device_factory_get_device_type (void) +{ + return PLUGIN_TYPE; +} + +/************************************************************************/ + +static gboolean +dev_get_attrs (GUdevDevice *udev_device, + const char **out_path, + char **out_driver) +{ + GUdevDevice *parent = NULL; + const char *driver, *path; + + g_return_val_if_fail (udev_device != NULL, FALSE); + g_return_val_if_fail (out_path != NULL, FALSE); + g_return_val_if_fail (out_driver != NULL, FALSE); + + path = g_udev_device_get_sysfs_path (udev_device); + if (!path) { + nm_log_warn (LOGD_HW, "couldn't determine device path; ignoring..."); + return FALSE; + } + + driver = g_udev_device_get_driver (udev_device); + if (!driver) { + /* Try the parent */ + parent = g_udev_device_get_parent (udev_device); + if (parent) + driver = g_udev_device_get_driver (parent); + } + + *out_path = path; + *out_driver = g_strdup (driver); + + g_clear_object (&parent); + return TRUE; +} + +static void +device_destroyed (gpointer user_data, GObject *dead) +{ + NMAtmManager *self = NM_ATM_MANAGER (user_data); + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE (self); + + priv->devices = g_slist_remove (priv->devices, dead); +} + +static void +adsl_add (NMAtmManager *self, GUdevDevice *udev_device) +{ + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE (self); + const char *ifname, *sysfs_path = NULL; + char *driver = NULL; + NMDevice *device; + + g_return_if_fail (udev_device != NULL); + + ifname = g_udev_device_get_name (udev_device); + if (!ifname) { + nm_log_warn (LOGD_HW, "failed to get device's interface name"); + return; + } + + nm_log_dbg (LOGD_HW, "(%s): found ATM device", ifname); + + if (dev_get_attrs (udev_device, &sysfs_path, &driver)) { + g_assert (sysfs_path); + + device = nm_device_adsl_new (sysfs_path, ifname, driver); + g_assert (device); + + priv->devices = g_slist_prepend (priv->devices, device); + g_object_weak_ref (G_OBJECT (device), device_destroyed, self); + + g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); + g_object_unref (device); + + g_free (driver); + } +} + +static void +adsl_remove (NMAtmManager *self, GUdevDevice *udev_device) +{ + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE (self); + const char *iface = g_udev_device_get_name (udev_device); + GSList *iter; + + nm_log_dbg (LOGD_HW, "(%s): removing ATM device", iface); + + for (iter = priv->devices; iter; iter = iter->next) { + NMDevice *device = iter->data; + + /* Match 'iface' not 'ip_iface' to the ATM device instead of the + * NAS bridge interface or PPPoE interface. + */ + if (g_strcmp0 (nm_device_get_iface (device), iface) != 0) + continue; + + g_object_weak_unref (G_OBJECT (iter->data), device_destroyed, self); + priv->devices = g_slist_remove (priv->devices, device); + g_signal_emit_by_name (device, NM_DEVICE_REMOVED); + break; + } +} + +static gboolean +query_devices (NMAtmManager *self) +{ + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE (self); + GUdevEnumerator *enumerator; + GList *devices, *iter; + + enumerator = g_udev_enumerator_new (priv->client); + g_udev_enumerator_add_match_subsystem (enumerator, "atm"); + g_udev_enumerator_add_match_is_initialized (enumerator); + devices = g_udev_enumerator_execute (enumerator); + for (iter = devices; iter; iter = g_list_next (iter)) { + adsl_add (self, G_UDEV_DEVICE (iter->data)); + g_object_unref (G_UDEV_DEVICE (iter->data)); + } + g_list_free (devices); + g_object_unref (enumerator); + + return G_SOURCE_REMOVE; +} + +static void +handle_uevent (GUdevClient *client, + const char *action, + GUdevDevice *device, + gpointer user_data) +{ + NMAtmManager *self = NM_ATM_MANAGER (user_data); + const char *subsys; + const char *ifindex; + guint64 seqnum; + + g_return_if_fail (action != NULL); + + /* A bit paranoid */ + subsys = g_udev_device_get_subsystem (device); + g_return_if_fail (!g_strcmp0 (subsys, "atm")); + + ifindex = g_udev_device_get_property (device, "IFINDEX"); + seqnum = g_udev_device_get_seqnum (device); + nm_log_dbg (LOGD_HW, "UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT, + action, subsys, g_udev_device_get_name (device), ifindex ? ifindex : "unknown", seqnum); + + if (!strcmp (action, "add")) + adsl_add (self, device); + else if (!strcmp (action, "remove")) + adsl_remove (self, device); +} + +/*********************************************************************/ + +static void +nm_atm_manager_init (NMAtmManager *self) +{ + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE (self); + const char *subsys[] = { "atm", NULL }; + + priv->client = g_udev_client_new (subsys); + g_signal_connect (priv->client, "uevent", G_CALLBACK (handle_uevent), self); + + priv->start_id = g_idle_add ((GSourceFunc) query_devices, self); +} + +static void +device_factory_interface_init (NMDeviceFactory *factory_iface) +{ +} + +static void +dispose (GObject *object) +{ + NMAtmManager *self = NM_ATM_MANAGER (object); + NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE (self); + GSList *iter; + + if (priv->client) + g_signal_handlers_disconnect_by_func (priv->client, handle_uevent, self); + g_clear_object (&priv->client); + + if (priv->start_id) { + g_source_remove (priv->start_id); + priv->start_id = 0; + } + + for (iter = priv->devices; iter; iter = iter->next) + g_object_weak_unref (G_OBJECT (iter->data), device_destroyed, self); + g_clear_pointer (&priv->devices, g_slist_free); + + G_OBJECT_CLASS (nm_atm_manager_parent_class)->dispose (object); +} + +static void +nm_atm_manager_class_init (NMAtmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMAtmManagerPrivate)); + + /* virtual methods */ + object_class->dispose = dispose; +} diff --git a/src/devices/adsl/nm-atm-manager.h b/src/devices/adsl/nm-atm-manager.h new file mode 100644 index 000000000..005252207 --- /dev/null +++ b/src/devices/adsl/nm-atm-manager.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2014 Red Hat, Inc. + */ + +#ifndef NM_ATM_MANAGER_H +#define NM_ATM_MANAGER_H + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define NM_TYPE_ATM_MANAGER (nm_atm_manager_get_type ()) +#define NM_ATM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ATM_MANAGER, NMAtmManager)) + +typedef struct { + GObject parent; +} NMAtmManager; + +typedef struct { + GObjectClass parent; +} NMAtmManagerClass; + +#endif /* NM_ATM_MANAGER_H */ + diff --git a/src/devices/adsl/nm-device-adsl-glue.h b/src/devices/adsl/nm-device-adsl-glue.h new file mode 100644 index 000000000..3a1b9108c --- /dev/null +++ b/src/devices/adsl/nm-device-adsl-glue.h @@ -0,0 +1,73 @@ +/* Generated by dbus-binding-tool; do not edit! */ + + +#ifndef __dbus_glib_marshal_nm_device_adsl_MARSHAL_H__ +#define __dbus_glib_marshal_nm_device_adsl_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +G_END_DECLS + +#endif /* __dbus_glib_marshal_nm_device_adsl_MARSHAL_H__ */ + +#include <dbus/dbus-glib.h> +static const DBusGMethodInfo dbus_glib_nm_device_adsl_methods[] = { +}; + +const DBusGObjectInfo dbus_glib_nm_device_adsl_object_info = { 1, + dbus_glib_nm_device_adsl_methods, + 0, +"\0", +"org.freedesktop.NetworkManager.Device.Adsl\0PropertiesChanged\0\0", +"org.freedesktop.NetworkManager.Device.Adsl\0Carrier\0carrier\0read\0\0" +}; + diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c new file mode 100644 index 000000000..0c35bb7ad --- /dev/null +++ b/src/devices/adsl/nm-device-adsl.c @@ -0,0 +1,640 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Pantelis Koukousoulas <pktoss@gmail.com> + */ + +#include <config.h> + +#include <sys/socket.h> +#include <linux/atmdev.h> +#include <linux/atmbr2684.h> + +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <stdlib.h> +#include <string.h> + +#include "nm-device-adsl.h" +#include "nm-device-private.h" +#include "NetworkManagerUtils.h" +#include "nm-logging.h" +#include "nm-enum-types.h" +#include "nm-dbus-manager.h" +#include "nm-platform.h" + +#include "ppp-manager/nm-ppp-manager.h" +#include "nm-setting-adsl.h" + +#include "nm-device-adsl-glue.h" + +G_DEFINE_TYPE (NMDeviceAdsl, nm_device_adsl, NM_TYPE_DEVICE) + +#define NM_DEVICE_ADSL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_ADSL, NMDeviceAdslPrivate)) + +/**********************************************/ + +typedef struct { + gboolean disposed; + guint carrier_poll_id; + int atm_index; + + /* PPP */ + NMPPPManager *ppp_manager; + + /* RFC 2684 bridging (PPPoE over ATM) */ + int brfd; + int nas_ifindex; + char * nas_ifname; +} NMDeviceAdslPrivate; + +/**************************************************************/ + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + return (NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER); +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMSettingAdsl *s_adsl; + const char *protocol; + + if (!NM_DEVICE_CLASS (nm_device_adsl_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + if (!nm_connection_is_type (connection, NM_SETTING_ADSL_SETTING_NAME)) + return FALSE; + + s_adsl = nm_connection_get_setting_adsl (connection); + if (!s_adsl) + return FALSE; + + /* FIXME: we don't yet support IPoATM */ + protocol = nm_setting_adsl_get_protocol (s_adsl); + if (g_strcmp0 (protocol, NM_SETTING_ADSL_PROTOCOL_IPOATM) == 0) + return FALSE; + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMSettingAdsl *s_adsl; + + /* + * We can't telepathically figure out the username, so if + * it wasn't given, we can't complete the connection. + */ + s_adsl = nm_connection_get_setting_adsl (connection); + if (s_adsl && !nm_setting_verify (NM_SETTING (s_adsl), NULL, error)) + return FALSE; + + nm_utils_complete_generic (connection, + NM_SETTING_ADSL_SETTING_NAME, + existing_connections, + _("ADSL connection %d"), + NULL, + FALSE); /* No IPv6 yet by default */ + + + return TRUE; +} + +/**************************************************************/ + +static void +set_nas_iface (NMDeviceAdsl *self, int idx, const char *name) +{ + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self); + + g_return_if_fail (name != NULL); + + g_warn_if_fail (priv->nas_ifindex <= 0); + priv->nas_ifindex = idx > 0 ? idx : nm_platform_link_get_ifindex (name); + g_warn_if_fail (priv->nas_ifindex > 0); + + g_warn_if_fail (priv->nas_ifname == NULL); + priv->nas_ifname = g_strdup (name); + + /* Update NAS interface's MAC address */ + nm_device_update_hw_address (NM_DEVICE (self)); +} + +static gboolean +br2684_create_iface (NMDeviceAdsl *self, NMSettingAdsl *s_adsl) +{ + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self); + const char *iface = nm_device_get_iface (NM_DEVICE (self)); + struct atm_newif_br2684 ni; + int err, fd; + gboolean success = FALSE; + guint num = 0; + + g_return_val_if_fail (s_adsl != NULL, FALSE); + + fd = socket (PF_ATMPVC, SOCK_DGRAM, ATM_AAL5); + if (fd < 0) { + nm_log_err (LOGD_ADSL, "(%s): failed to open ATM control socket (%d)", + iface, errno); + return FALSE; + } + + memset (&ni, 0, sizeof (ni)); + ni.backend_num = ATM_BACKEND_BR2684; + ni.media = BR2684_MEDIA_ETHERNET; + ni.mtu = 1500; + + /* Loop attempting to create an interface that doesn't exist yet. The + * kernel can create one for us automatically, but due to API issues it + * cannot return that name to us. Since we want to know the name right + * away, just brute-force it. + */ + while (num < 10000) { + memset (&ni.ifname, 0, sizeof (ni.ifname)); + g_snprintf (ni.ifname, sizeof (ni.ifname), "nas%d", num); + + err = ioctl (fd, ATM_NEWBACKENDIF, &ni); + if (err == 0) { + set_nas_iface (self, -1, ni.ifname); + nm_log_info (LOGD_ADSL, "(%s): using NAS interface %s (%d)", + iface, priv->nas_ifname, priv->nas_ifindex); + success = TRUE; + break; + } else if (errno == -EEXIST) { + /* Try again */ + num++; + } else { + nm_log_warn (LOGD_ADSL, "(%s): failed to create br2684 interface (%d)", + iface, errno); + break; + } + } + + close (fd); + return success; +} + +static gboolean +br2684_assign_vcc (NMDeviceAdsl *self, NMSettingAdsl *s_adsl) +{ + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self); + const char *iface = nm_device_get_iface (NM_DEVICE (self)); + struct sockaddr_atmpvc addr; + struct atm_backend_br2684 be; + struct atm_qos qos; + int err, bufsize = 8192; + const char *encapsulation; + gboolean is_llc; + + g_return_val_if_fail (priv->brfd == -1, FALSE); + g_return_val_if_fail (priv->nas_ifname != NULL, FALSE); + + priv->brfd = socket (PF_ATMPVC, SOCK_DGRAM, ATM_AAL5); + if (priv->brfd < 0) { + nm_log_err (LOGD_ADSL, "(%s): failed to open ATM control socket (%d)", + iface, errno); + return FALSE; + } + + err = setsockopt (priv->brfd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof (bufsize)); + if (err != 0) { + nm_log_err (LOGD_ADSL, "(%s): failed to set SNDBUF option (%d)", + iface, errno); + goto error; + } + + /* QoS */ + memset (&qos, 0, sizeof (qos)); + qos.aal = ATM_AAL5; + qos.txtp.traffic_class = ATM_UBR; + qos.txtp.max_sdu = 1524; + qos.txtp.pcr = ATM_MAX_PCR; + qos.rxtp = qos.txtp; + + err = setsockopt (priv->brfd, SOL_ATM, SO_ATMQOS, &qos, sizeof (qos)); + if (err != 0) { + nm_log_err (LOGD_ADSL, "(%s): failed to set QoS (%d)", + iface, errno); + goto error; + } + + encapsulation = nm_setting_adsl_get_encapsulation (s_adsl); + + /* VPI/VCI */ + memset (&addr, 0, sizeof (addr)); + addr.sap_family = AF_ATMPVC; + addr.sap_addr.itf = priv->atm_index; + addr.sap_addr.vpi = (guint16) nm_setting_adsl_get_vpi (s_adsl); + addr.sap_addr.vci = (int) nm_setting_adsl_get_vci (s_adsl); + + nm_log_dbg (LOGD_ADSL, "(%s): assigning address %d.%d.%d encapsulation %s", + nm_device_get_iface (NM_DEVICE (self)), + priv->atm_index, addr.sap_addr.vpi, addr.sap_addr.vci, + encapsulation); + + err = connect (priv->brfd, (struct sockaddr*) &addr, sizeof (addr)); + if (err != 0) { + nm_log_err (LOGD_ADSL, "(%s): failed to set VPI/VCI (%d)", + iface, errno); + goto error; + } + + /* And last attach the VCC to the interface */ + is_llc = (g_strcmp0 (encapsulation, "llc") == 0); + + memset (&be, 0, sizeof (be)); + be.backend_num = ATM_BACKEND_BR2684; + be.ifspec.method = BR2684_FIND_BYIFNAME; + strcpy (be.ifspec.spec.ifname, priv->nas_ifname); + be.fcs_in = BR2684_FCSIN_NO; + be.fcs_out = BR2684_FCSOUT_NO; + be.encaps = is_llc ? BR2684_ENCAPS_LLC : BR2684_ENCAPS_VC; + err = ioctl (priv->brfd, ATM_SETBACKEND, &be); + if (err != 0) { + nm_log_err (LOGD_ADSL, "(%s): failed to attach VCC (%d)", + iface, errno); + goto error; + } + + return TRUE; + +error: + close (priv->brfd); + priv->brfd = -1; + return FALSE; +} + +static void +link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDeviceAdsl *device_adsl) +{ + if (change_type == NM_PLATFORM_SIGNAL_REMOVED) { + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (device_adsl); + NMDevice *device = NM_DEVICE (device_adsl); + + /* This only gets called for PPPoE connections and "nas" interfaces */ + + if (priv->nas_ifindex >= 0 && ifindex == priv->nas_ifindex) { + /* NAS device went away for some reason; kill the connection */ + nm_log_dbg (LOGD_ADSL, "(%s): NAS interface disappeared", + nm_device_get_iface (device)); + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BR2684_FAILED); + } + } +} + +static NMActStageReturn +act_stage2_config (NMDevice *device, NMDeviceStateReason *out_reason) +{ + NMDeviceAdsl *self = NM_DEVICE_ADSL (device); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self); + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + NMSettingAdsl *s_adsl; + const char *protocol; + + g_assert (out_reason); + + s_adsl = nm_connection_get_setting_adsl (nm_device_get_connection (device)); + g_assert (s_adsl); + + protocol = nm_setting_adsl_get_protocol (s_adsl); + nm_log_dbg (LOGD_ADSL, "(%s): using ADSL protocol '%s'", + nm_device_get_iface (device), protocol); + + if (g_strcmp0 (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE) == 0) { + + /* PPPoE needs RFC2684 bridging before we can do PPP over it */ + if (!br2684_create_iface (self, s_adsl)) { + *out_reason = NM_DEVICE_STATE_REASON_BR2684_FAILED; + goto done; + } + + /* Set up the VCC */ + if (!br2684_assign_vcc (self, s_adsl)) { + *out_reason = NM_DEVICE_STATE_REASON_BR2684_FAILED; + goto done; + } + + /* Watch for the 'nas' interface going away */ + g_signal_connect (nm_platform_get (), NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK (link_changed_cb), + self); + + nm_log_dbg (LOGD_ADSL, "(%s): ATM setup successful", nm_device_get_iface (device)); + + /* otherwise we're good for stage3 */ + nm_platform_link_set_up (priv->nas_ifindex); + ret = NM_ACT_STAGE_RETURN_SUCCESS; + + } else if (g_strcmp0 (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA) == 0) { + /* PPPoA doesn't need anything special */ + ret = NM_ACT_STAGE_RETURN_SUCCESS; + } else { + nm_log_warn (LOGD_ADSL, "(%s): unhandled ADSL protocol '%s'", + nm_device_get_iface (device), protocol); + } + +done: + return ret; +} + +static void +ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ip4_config (NMPPPManager *ppp_manager, + const char *iface, + NMIP4Config *config, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + /* Ignore PPP IP4 events that come in after initial configuration */ + if (nm_device_activate_ip4_state_in_conf (device)) { + nm_device_set_ip_iface (device, iface); + nm_device_activate_schedule_ip4_config_result (device, config); + } +} + +static NMActStageReturn +act_stage3_ip4_config_start (NMDevice *device, + NMIP4Config **out_config, + NMDeviceStateReason *reason) +{ + NMDeviceAdsl *self = NM_DEVICE_ADSL (device); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self); + NMConnection *connection; + NMSettingAdsl *s_adsl; + NMActRequest *req; + GError *err = NULL; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + const char *iface = nm_device_get_iface (device); + const char *ppp_iface; + + req = nm_device_get_act_request (device); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (req); + + s_adsl = nm_connection_get_setting_adsl (connection); + g_assert (s_adsl); + + /* PPPoE uses the NAS interface, not the ATM interface */ + if (g_strcmp0 (nm_setting_adsl_get_protocol (s_adsl), NM_SETTING_ADSL_PROTOCOL_PPPOE) == 0) { + g_assert (priv->nas_ifname); + ppp_iface = priv->nas_ifname; + + nm_log_dbg (LOGD_ADSL, "(%s): starting PPPoE on NAS interface %s", + iface, priv->nas_ifname); + } else { + ppp_iface = iface; + nm_log_dbg (LOGD_ADSL, "(%s): starting PPPoA", iface); + } + + priv->ppp_manager = nm_ppp_manager_new (ppp_iface); + if (nm_ppp_manager_start (priv->ppp_manager, req, nm_setting_adsl_get_username (s_adsl), 30, &err)) { + g_signal_connect (priv->ppp_manager, "state-changed", + G_CALLBACK (ppp_state_changed), + self); + g_signal_connect (priv->ppp_manager, "ip4-config", + G_CALLBACK (ppp_ip4_config), + self); + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else { + nm_log_warn (LOGD_ADSL, "(%s): PPP failed to start: %s", iface, err->message); + g_error_free (err); + + g_object_unref (priv->ppp_manager); + priv->ppp_manager = NULL; + + *reason = NM_DEVICE_STATE_REASON_PPP_START_FAILED; + } + + return ret; +} + +static void +deactivate (NMDevice *device) +{ + NMDeviceAdsl *self = NM_DEVICE_ADSL (device); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self); + + if (priv->ppp_manager) { + g_object_unref (priv->ppp_manager); + priv->ppp_manager = NULL; + } + + g_signal_handlers_disconnect_by_func (nm_platform_get (), G_CALLBACK (link_changed_cb), device); + + if (priv->brfd >= 0) { + close (priv->brfd); + priv->brfd = -1; + } + + /* FIXME: kernel has no way of explicitly deleting the 'nasX' interface yet, + * so it gets leaked. It does get destroyed when it's no longer in use, + * but we have no control over that. + */ + if (priv->nas_ifindex >= 0) + priv->nas_ifindex = -1; + g_free (priv->nas_ifname); + priv->nas_ifname = NULL; + + /* Poke NMDevice to notice that our hw_address is no longer valid */ + nm_device_update_hw_address (NM_DEVICE (self)); +} + +/**************************************************************/ + +static guint +get_hw_address_length (NMDevice *device, gboolean *out_permanent) +{ + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (device); + + return priv->nas_ifname ? ETH_ALEN : 0; +} + +static gboolean +carrier_update_cb (gpointer user_data) +{ + NMDeviceAdsl *self = NM_DEVICE_ADSL (user_data); + int carrier; + char *path; + const char *iface; + + iface = nm_device_get_iface (NM_DEVICE (self)); + + path = g_strdup_printf ("/sys/class/atm/%s/carrier", + ASSERT_VALID_PATH_COMPONENT (iface)); + carrier = (int) nm_platform_sysctl_get_int_checked (path, 10, 0, 1, -1); + g_free (path); + + if (carrier != -1) + nm_device_set_carrier (NM_DEVICE (self), carrier); + return TRUE; +} + +/**************************************************************/ + +NMDevice * +nm_device_adsl_new (const char *udi, + const char *iface, + const char *driver) +{ + g_return_val_if_fail (udi != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_ADSL, + NM_DEVICE_UDI, udi, + NM_DEVICE_IFACE, iface, + NM_DEVICE_DRIVER, driver, + NM_DEVICE_TYPE_DESC, "ADSL", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_ADSL, + NULL); +} + +static int +get_atm_index (const char *iface) +{ + char *path; + int idx; + + path = g_strdup_printf ("/sys/class/atm/%s/atmindex", + ASSERT_VALID_PATH_COMPONENT (iface)); + idx = (int) nm_platform_sysctl_get_int_checked (path, 10, 0, G_MAXINT, -1); + g_free (path); + + return idx; +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMDeviceAdslPrivate *priv; + + object = G_OBJECT_CLASS (nm_device_adsl_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + priv = NM_DEVICE_ADSL_GET_PRIVATE (object); + + priv->atm_index = get_atm_index (nm_device_get_iface (NM_DEVICE (object))); + if (priv->atm_index < 0) { + nm_log_err (LOGD_ADSL, "(%s): error reading ATM device index", + nm_device_get_iface (NM_DEVICE (object))); + g_object_unref (object); + return NULL; + } else { + nm_log_dbg (LOGD_ADSL, "(%s): ATM device index %d", + nm_device_get_iface (NM_DEVICE (object)), priv->atm_index); + } + + /* Poll the carrier */ + priv->carrier_poll_id = g_timeout_add_seconds (5, carrier_update_cb, object); + + return object; +} + +static void +dispose (GObject *object) +{ + NMDeviceAdsl *self = NM_DEVICE_ADSL (object); + NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self); + + if (priv->disposed) { + G_OBJECT_CLASS (nm_device_adsl_parent_class)->dispose (object); + return; + } + + priv->disposed = TRUE; + + if (priv->carrier_poll_id) { + g_source_remove (priv->carrier_poll_id); + priv->carrier_poll_id = 0; + } + + g_signal_handlers_disconnect_by_func (nm_platform_get (), G_CALLBACK (link_changed_cb), self); + + g_free (priv->nas_ifname); + priv->nas_ifname = NULL; + + G_OBJECT_CLASS (nm_device_adsl_parent_class)->dispose (object); +} + +static void +nm_device_adsl_init (NMDeviceAdsl *self) +{ +} + +static void +nm_device_adsl_class_init (NMDeviceAdslClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceAdslPrivate)); + + object_class->constructor = constructor; + object_class->dispose = dispose; + + parent_class->get_generic_capabilities = get_generic_capabilities; + + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->complete_connection = complete_connection; + + parent_class->get_hw_address_length = get_hw_address_length; + parent_class->act_stage2_config = act_stage2_config; + parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start; + parent_class->deactivate = deactivate; + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_adsl_object_info); +} diff --git a/src/devices/adsl/nm-device-adsl.h b/src/devices/adsl/nm-device-adsl.h new file mode 100644 index 000000000..bbd0e63f0 --- /dev/null +++ b/src/devices/adsl/nm-device-adsl.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Author: Pantelis Koukousoulas <pktoss@gmail.com> + * Copyright (C) 2009 - 2011 Red Hat Inc. + */ + +#ifndef NM_DEVICE_ADSL_H +#define NM_DEVICE_ADSL_H + +#include <glib-object.h> + +// Parent class +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_ADSL (nm_device_adsl_get_type ()) +#define NM_DEVICE_ADSL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_ADSL, NMDeviceAdsl)) +#define NM_DEVICE_ADSL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_ADSL, NMDeviceAdslClass)) +#define NM_IS_DEVICE_ADSL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_ADSL)) +#define NM_IS_DEVICE_ADSL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_ADSL)) +#define NM_DEVICE_ADSL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_ADSL, NMDeviceAdslClass)) + +typedef struct { + NMDevice parent; +} NMDeviceAdsl; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceAdslClass; + +GType nm_device_adsl_get_type (void); + +NMDevice *nm_device_adsl_new (const char *udi, + const char *iface, + const char *driver); + +G_END_DECLS + +#endif /* NM_DEVICE_ADSL_H */ diff --git a/src/devices/bluetooth/Makefile.am b/src/devices/bluetooth/Makefile.am new file mode 100644 index 000000000..639a1ad7a --- /dev/null +++ b/src/devices/bluetooth/Makefile.am @@ -0,0 +1,74 @@ +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_srcdir}/src/devices/wwan \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-bluetooth"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +GLIB_GENERATED = nm-bt-enum-types.h nm-bt-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_bt_enum_types_sources = $(srcdir)/nm-device-bt.h + +nm-device-bt-glue.h: $(top_srcdir)/introspection/nm-device-bt.xml + dbus-binding-tool --prefix=nm_device_bt --mode=glib-server --output=$@ $< + +BUILT_SOURCES = $(GLIB_GENERATED) nm-device-bt-glue.h + +pkglib_LTLIBRARIES = libnm-device-plugin-bluetooth.la + +SYMBOL_VIS_FILE=$(srcdir)/exports.ver + +libnm_device_plugin_bluetooth_la_SOURCES = \ + nm-bluez-manager.c \ + nm-bluez-manager.h \ + nm-bluez-common.h \ + nm-bluez-device.c \ + nm-bluez-device.h \ + nm-bluez4-adapter.c \ + nm-bluez4-adapter.h \ + nm-bluez4-manager.c \ + nm-bluez4-manager.h \ + nm-bluez5-manager.c \ + nm-bluez5-manager.h \ + \ + nm-device-bt.c \ + nm-device-bt.h \ + \ + $(BUILT_SOURCES) + +libnm_device_plugin_bluetooth_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_bluetooth_la_LIBADD = \ + $(top_builddir)/src/devices/wwan/libnm-wwan.la \ + $(DBUS_LIBS) \ + $(GUDEV_LIBS) + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) + +if ENABLE_TESTS + +check-local: + $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-bluetooth.so $(SYMBOL_VIS_FILE) + +endif + diff --git a/src/devices/bluetooth/Makefile.in b/src/devices/bluetooth/Makefile.in new file mode 100644 index 000000000..3145c52da --- /dev/null +++ b/src/devices/bluetooth/Makefile.in @@ -0,0 +1,864 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/devices/bluetooth +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/build-aux/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \ + $(top_srcdir)/m4/compiler_warnings.m4 \ + $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gnome-code-coverage.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkglibdir)" +LTLIBRARIES = $(pkglib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libnm_device_plugin_bluetooth_la_DEPENDENCIES = \ + $(top_builddir)/src/devices/wwan/libnm-wwan.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am__objects_1 = nm-bt-enum-types.lo +am__objects_2 = $(am__objects_1) +am_libnm_device_plugin_bluetooth_la_OBJECTS = nm-bluez-manager.lo \ + nm-bluez-device.lo nm-bluez4-adapter.lo nm-bluez4-manager.lo \ + nm-bluez5-manager.lo nm-device-bt.lo $(am__objects_2) +libnm_device_plugin_bluetooth_la_OBJECTS = \ + $(am_libnm_device_plugin_bluetooth_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libnm_device_plugin_bluetooth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(libnm_device_plugin_bluetooth_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libnm_device_plugin_bluetooth_la_SOURCES) +DIST_SOURCES = $(libnm_device_plugin_bluetooth_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CKDB_PATH = @CKDB_PATH@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@ +DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SYS_DIR = @DBUS_SYS_DIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHCLIENT_PATH = @DHCLIENT_PATH@ +DHCPCD_PATH = @DHCPCD_PATH@ +DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@ +DLLTOOL = @DLLTOOL@ +DNSMASQ_PATH = @DNSMASQ_PATH@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MAKEFILE = @GLIB_MAKEFILE@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +IPTABLES_PATH = @IPTABLES_PATH@ +IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@ +IWMX_SDK_LIBS = @IWMX_SDK_LIBS@ +KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDL = @LIBDL@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBNDP_CFLAGS = @LIBNDP_CFLAGS@ +LIBNDP_LIBS = @LIBNDP_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@ +LIBSOUP_LIBS = @LIBSOUP_LIBS@ +LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@ +LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MM_GLIB_CFLAGS = @MM_GLIB_CFLAGS@ +MM_GLIB_LIBS = @MM_GLIB_LIBS@ +MOC = @MOC@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEWT_CFLAGS = @NEWT_CFLAGS@ +NEWT_LIBS = @NEWT_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NM_MAJOR_VERSION = @NM_MAJOR_VERSION@ +NM_MICRO_VERSION = @NM_MICRO_VERSION@ +NM_MINOR_VERSION = @NM_MINOR_VERSION@ +NM_MODIFY_SYSTEM_POLICY = @NM_MODIFY_SYSTEM_POLICY@ +NM_VERSION = @NM_VERSION@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POLKIT_CFLAGS = @POLKIT_CFLAGS@ +POLKIT_LIBS = @POLKIT_LIBS@ +POSUB = @POSUB@ +PPPD_PATH = @PPPD_PATH@ +PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@ +PPPOE_PATH = @PPPOE_PATH@ +QT_CFLAGS = @QT_CFLAGS@ +QT_LIBS = @QT_LIBS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@ +SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@ +SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@ +SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +SYSTEM_CA_PATH = @SYSTEM_CA_PATH@ +UDEV_BASE_DIR = @UDEV_BASE_DIR@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND_RULES = @VALGRIND_RULES@ +VAPIGEN = @VAPIGEN@ +VAPIGEN_MAKEFILE = @VAPIGEN_MAKEFILE@ +VAPIGEN_VAPIDIR = @VAPIGEN_VAPIDIR@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nmbinary = @nmbinary@ +nmconfdir = @nmconfdir@ +nmdatadir = @nmdatadir@ +nmrundir = @nmrundir@ +nmstatedir = @nmstatedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +with_dhclient = @with_dhclient@ +with_dhcpcd = @with_dhcpcd@ +with_netconfig = @with_netconfig@ +with_resolvconf = @with_resolvconf@ +with_valgrind = @with_valgrind@ +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_srcdir}/src/devices/wwan \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-bluetooth"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +GLIB_GENERATED = nm-bt-enum-types.h nm-bt-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_bt_enum_types_sources = $(srcdir)/nm-device-bt.h +BUILT_SOURCES = $(GLIB_GENERATED) nm-device-bt-glue.h +pkglib_LTLIBRARIES = libnm-device-plugin-bluetooth.la +SYMBOL_VIS_FILE = $(srcdir)/exports.ver +libnm_device_plugin_bluetooth_la_SOURCES = \ + nm-bluez-manager.c \ + nm-bluez-manager.h \ + nm-bluez-common.h \ + nm-bluez-device.c \ + nm-bluez-device.h \ + nm-bluez4-adapter.c \ + nm-bluez4-adapter.h \ + nm-bluez4-manager.c \ + nm-bluez4-manager.h \ + nm-bluez5-manager.c \ + nm-bluez5-manager.h \ + \ + nm-device-bt.c \ + nm-device-bt.h \ + \ + $(BUILT_SOURCES) + +libnm_device_plugin_bluetooth_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_bluetooth_la_LIBADD = \ + $(top_builddir)/src/devices/wwan/libnm-wwan.la \ + $(DBUS_LIBS) \ + $(GUDEV_LIBS) + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/devices/bluetooth/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/devices/bluetooth/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ + } + +uninstall-pkglibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ + done + +clean-pkglibLTLIBRARIES: + -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) + @list='$(pkglib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libnm-device-plugin-bluetooth.la: $(libnm_device_plugin_bluetooth_la_OBJECTS) $(libnm_device_plugin_bluetooth_la_DEPENDENCIES) $(EXTRA_libnm_device_plugin_bluetooth_la_DEPENDENCIES) + $(AM_V_CCLD)$(libnm_device_plugin_bluetooth_la_LINK) -rpath $(pkglibdir) $(libnm_device_plugin_bluetooth_la_OBJECTS) $(libnm_device_plugin_bluetooth_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-bluez-device.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-bluez-manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-bluez4-adapter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-bluez4-manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-bluez5-manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-bt-enum-types.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-device-bt.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +@ENABLE_TESTS_FALSE@check-local: +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(pkglibdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-pkglibLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkglibLTLIBRARIES + +.MAKE: all check check-am install install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am check-local clean \ + clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkglibLTLIBRARIES install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkglibLTLIBRARIES + +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +nm-device-bt-glue.h: $(top_srcdir)/introspection/nm-device-bt.xml + dbus-binding-tool --prefix=nm_device_bt --mode=glib-server --output=$@ $< + +@ENABLE_TESTS_TRUE@check-local: +@ENABLE_TESTS_TRUE@ $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-bluetooth.so $(SYMBOL_VIS_FILE) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/devices/bluetooth/exports.ver b/src/devices/bluetooth/exports.ver new file mode 100644 index 000000000..d2c451244 --- /dev/null +++ b/src/devices/bluetooth/exports.ver @@ -0,0 +1,7 @@ +{ +global: + nm_device_factory_create; + nm_device_factory_get_device_type; +local: + *; +}; diff --git a/src/devices/bluetooth/nm-bluez-common.h b/src/devices/bluetooth/nm-bluez-common.h new file mode 100644 index 000000000..f80cfc2e3 --- /dev/null +++ b/src/devices/bluetooth/nm-bluez-common.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef NM_BLUEZ_COMMON_H +#define NM_BLUEZ_COMMON_H + +#include <config.h> + +#define BLUETOOTH_CONNECT_DUN "dun" +#define BLUETOOTH_CONNECT_NAP "nap" + +#define BLUEZ_SERVICE "org.bluez" + +#define BLUEZ_MANAGER_PATH "/" +#define OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager" + +#define BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1" +#define BLUEZ5_DEVICE_INTERFACE "org.bluez.Device1" +#define BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1" + +#define BLUEZ4_MANAGER_INTERFACE "org.bluez.Manager" +#define BLUEZ4_ADAPTER_INTERFACE "org.bluez.Adapter" +#define BLUEZ4_DEVICE_INTERFACE "org.bluez.Device" +#define BLUEZ4_SERIAL_INTERFACE "org.bluez.Serial" +#define BLUEZ4_NETWORK_INTERFACE "org.bluez.Network" + +#endif /* NM_BLUEZ_COMMON_H */ + diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c new file mode 100644 index 000000000..4c448a376 --- /dev/null +++ b/src/devices/bluetooth/nm-bluez-device.c @@ -0,0 +1,1212 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2012 Red Hat, Inc. + * Copyright (C) 2013 Intel Corporation. + */ + +#include <glib.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <string.h> +#include <net/ethernet.h> +#include <netinet/ether.h> + +#include "NetworkManager.h" +#include "nm-setting-bluetooth.h" + +#include "nm-bluez-common.h" +#include "nm-bluez-device.h" +#include "nm-logging.h" +#include "nm-utils.h" +#include "nm-settings-connection.h" + + +G_DEFINE_TYPE (NMBluezDevice, nm_bluez_device, G_TYPE_OBJECT) + +#define NM_BLUEZ_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_BLUEZ_DEVICE, NMBluezDevicePrivate)) + +typedef struct { + char *path; + GDBusConnection *dbus_connection; + + GDBusProxy *proxy; + + GDBusProxy *adapter5; + gboolean adapter_powered; + + int bluez_version; + + gboolean initialized; + gboolean usable; + NMBluetoothCapabilities connection_bt_type; + + char *address; + guint8 bin_address[ETH_ALEN]; + char *name; + guint32 capabilities; + gboolean connected; + + char *bt_iface; + + NMConnectionProvider *provider; + GSList *connections; + + NMConnection *pan_connection; + NMConnection *pan_connection_original; + gboolean pan_connection_no_autocreate; +} NMBluezDevicePrivate; + + +enum { + PROP_0, + PROP_PATH, + PROP_ADDRESS, + PROP_NAME, + PROP_CAPABILITIES, + PROP_USABLE, + PROP_CONNECTED, + + LAST_PROP +}; + +/* Signals */ +enum { + INITIALIZED, + REMOVED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + + +static void cp_connection_added (NMConnectionProvider *provider, + NMConnection *connection, NMBluezDevice *self); +static gboolean connection_compatible (NMBluezDevice *self, NMConnection *connection); + + +#define VARIANT_IS_OF_TYPE_BOOLEAN(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_BOOLEAN) )) +#define VARIANT_IS_OF_TYPE_STRING(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING) )) +#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_OBJECT_PATH) )) +#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING_ARRAY) )) + +/***********************************************************/ + +const char * +nm_bluez_device_get_path (NMBluezDevice *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL); + + return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->path; +} + +const char * +nm_bluez_device_get_address (NMBluezDevice *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL); + + return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->address; +} + +gboolean +nm_bluez_device_get_initialized (NMBluezDevice *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE); + + return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->initialized; +} + +gboolean +nm_bluez_device_get_usable (NMBluezDevice *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE); + + return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->usable; +} + +const char * +nm_bluez_device_get_name (NMBluezDevice *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL); + + return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->name; +} + +guint32 +nm_bluez_device_get_capabilities (NMBluezDevice *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), 0); + + return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->capabilities; +} + +gboolean +nm_bluez_device_get_connected (NMBluezDevice *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE); + + return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->connected; +} + +static void +pan_connection_check_create (NMBluezDevice *self) +{ + NMConnection *connection; + NMConnection *added; + NMSetting *setting; + char *uuid, *id; + GByteArray *bdaddr_array; + GError *error = NULL; + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + + g_return_if_fail (priv->capabilities & NM_BT_CAPABILITY_NAP); + g_return_if_fail (priv->connections == NULL); + g_return_if_fail (priv->name); + + if (priv->pan_connection || priv->pan_connection_no_autocreate) { + /* already have a connection or we don't want to create one, nothing to do. */ + return; + } + + /* Only try once to create a connection. If it does not succeed, we do not try again. Also, + * if the connection gets deleted later, do not create another one for this device. */ + priv->pan_connection_no_autocreate = TRUE; + + /* create a new connection */ + + connection = nm_connection_new (); + + /* Setting: Connection */ + uuid = nm_utils_uuid_generate (); + id = g_strdup_printf (_("%s Network"), priv->name); + setting = nm_setting_connection_new (); + g_object_set (setting, + NM_SETTING_CONNECTION_ID, id, + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME, + NULL); + nm_connection_add_setting (connection, setting); + + /* Setting: Bluetooth */ + bdaddr_array = g_byte_array_sized_new (sizeof (priv->bin_address)); + g_byte_array_append (bdaddr_array, priv->bin_address, sizeof (priv->bin_address)); + setting = nm_setting_bluetooth_new (); + g_object_set (G_OBJECT (setting), + NM_SETTING_BLUETOOTH_BDADDR, bdaddr_array, + NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL); + nm_connection_add_setting (connection, setting); + g_byte_array_free (bdaddr_array, TRUE); + + /* Setting: IPv4 */ + setting = nm_setting_ip4_config_new (); + g_object_set (G_OBJECT (setting), + NM_SETTING_IP4_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP4_CONFIG_MAY_FAIL, FALSE, + NULL); + nm_connection_add_setting (connection, setting); + + /* Setting: IPv6 */ + setting = nm_setting_ip6_config_new (); + g_object_set (G_OBJECT (setting), + NM_SETTING_IP6_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_MAY_FAIL, TRUE, + NULL); + nm_connection_add_setting (connection, setting); + + /* Adding a new connection raises a signal which eventually calls check_emit_usable (again) + * which then already finds the suitable connection in priv->connections. This is confusing, + * so block the signal. check_emit_usable will succeed after this function call returns. */ + g_signal_handlers_block_by_func (priv->provider, cp_connection_added, self); + added = nm_connection_provider_add_connection (priv->provider, connection, FALSE, &error); + g_signal_handlers_unblock_by_func (priv->provider, cp_connection_added, self); + + if (added) { + g_assert (!g_slist_find (priv->connections, added)); + g_assert (connection_compatible (self, added)); + g_assert (nm_connection_compare (added, connection, NM_SETTING_COMPARE_FLAG_EXACT)); + + priv->connections = g_slist_prepend (priv->connections, g_object_ref (added)); + priv->pan_connection = added; + priv->pan_connection_original = connection; + nm_log_dbg (LOGD_BT, "bluez[%s] added new Bluetooth connection for NAP device: '%s' (%s)", priv->path, id, uuid); + } else { + nm_log_warn (LOGD_BT, "bluez[%s] couldn't add new Bluetooth connection for NAP device: '%s' (%s): %d / %s", + priv->path, id, uuid, error ? error->code : -1, + (error && error->message) ? error->message : "(unknown)"); + g_clear_error (&error); + + g_object_unref (connection); + } + + g_free (id); + g_free (uuid); +} + +static void +check_emit_usable (NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + gboolean new_usable; + + /* only expect the supported capabilities set. */ + g_assert (priv->bluez_version != 4 || ((priv->capabilities & ~(NM_BT_CAPABILITY_NAP | NM_BT_CAPABILITY_DUN)) == NM_BT_CAPABILITY_NONE )); + g_assert (priv->bluez_version != 5 || ((priv->capabilities & ~(NM_BT_CAPABILITY_NAP )) == NM_BT_CAPABILITY_NONE )); + + new_usable = (priv->initialized && priv->capabilities && priv->name && + ((priv->bluez_version == 4) || + (priv->bluez_version == 5 && priv->adapter5 && priv->adapter_powered) ) && + priv->dbus_connection && priv->address); + + if (!new_usable) + goto END; + + if (priv->connections) + goto END; + + if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) { + /* non NAP devices are only usable, if they already have a connection. */ + new_usable = FALSE; + goto END; + } + + pan_connection_check_create (self); + new_usable = !!priv->pan_connection; + +END: + if (new_usable != priv->usable) { + priv->usable = new_usable; + g_object_notify (G_OBJECT (self), NM_BLUEZ_DEVICE_USABLE); + } +} + +/********************************************************************/ + +static gboolean +connection_compatible (NMBluezDevice *self, NMConnection *connection) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + NMSettingBluetooth *s_bt; + const char *bt_type; + const GByteArray *bdaddr; + + if (!nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) + return FALSE; + + s_bt = nm_connection_get_setting_bluetooth (connection); + if (!s_bt) + return FALSE; + + if (!priv->address) { + /* unless address is set, bin_address is not initialized. */ + return FALSE; + } + bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt); + if (!bdaddr || bdaddr->len != ETH_ALEN) + return FALSE; + if (memcmp (bdaddr->data, priv->bin_address, ETH_ALEN) != 0) + return FALSE; + + bt_type = nm_setting_bluetooth_get_connection_type (s_bt); + if ( g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN) + && !(priv->capabilities & NM_BT_CAPABILITY_DUN)) + return FALSE; + + if ( g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU) + && !(priv->capabilities & NM_BT_CAPABILITY_NAP)) + return FALSE; + + return TRUE; +} + +static void +_internal_add_connection (NMBluezDevice *self, NMConnection *connection) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + + if (!g_slist_find (priv->connections, connection)) { + priv->connections = g_slist_prepend (priv->connections, g_object_ref (connection)); + check_emit_usable (self); + } +} + +static void +cp_connection_added (NMConnectionProvider *provider, + NMConnection *connection, + NMBluezDevice *self) +{ + if (connection_compatible (self, connection)) + _internal_add_connection (self, connection); +} + +static void +cp_connection_removed (NMConnectionProvider *provider, + NMConnection *connection, + NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + + if (g_slist_find (priv->connections, connection)) { + priv->connections = g_slist_remove (priv->connections, connection); + if (priv->pan_connection == connection) { + priv->pan_connection = NULL; + g_clear_object (&priv->pan_connection_original); + } + g_object_unref (connection); + check_emit_usable (self); + } +} + +static void +cp_connection_updated (NMConnectionProvider *provider, + NMConnection *connection, + NMBluezDevice *self) +{ + if (connection_compatible (self, connection)) + _internal_add_connection (self, connection); + else + cp_connection_removed (provider, connection, self); +} + +static void +load_connections (NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + const GSList *connections, *iter; + + connections = nm_connection_provider_get_connections (priv->provider); + for (iter = connections; iter; iter = g_slist_next (iter)) + cp_connection_added (priv->provider, NM_CONNECTION (iter->data), self); +} + +/***********************************************************/ + +static void +bluez_disconnect_cb (GDBusConnection *dbus_connection, + GAsyncResult *res, + gpointer user_data) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (user_data); + GError *error = NULL; + GVariant *variant; + + variant = g_dbus_connection_call_finish (dbus_connection, res, &error); + if (!variant) { + if (!strstr (error->message, "org.bluez.Error.NotConnected")) + nm_log_warn (LOGD_BT, "bluez[%s]: failed to disconnect: %s", priv->path, error->message); + g_error_free (error); + } else + g_variant_unref (variant); + + g_object_unref (NM_BLUEZ_DEVICE (user_data)); +} + +void +nm_bluez_device_disconnect (NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GVariant *args = NULL; + const char *dbus_iface; + + g_return_if_fail (priv->dbus_connection); + + if (priv->bluez_version == 5) { + g_return_if_fail (priv->connection_bt_type == NM_BT_CAPABILITY_NAP); + dbus_iface = BLUEZ5_NETWORK_INTERFACE; + } else if (priv->bluez_version == 4 && priv->connection_bt_type == NM_BT_CAPABILITY_DUN) { + /* Can't pass a NULL interface name through dbus to bluez, so just + * ignore the disconnect if the interface isn't known. + */ + if (!priv->bt_iface) + return; + + args = g_variant_new ("(s)", priv->bt_iface), + dbus_iface = BLUEZ4_SERIAL_INTERFACE; + } else { + g_return_if_fail (priv->bluez_version == 4 && priv->connection_bt_type == NM_BT_CAPABILITY_NAP); + dbus_iface = BLUEZ4_NETWORK_INTERFACE; + } + + g_dbus_connection_call (priv->dbus_connection, + BLUEZ_SERVICE, + priv->path, + dbus_iface, + "Disconnect", + args ? args : g_variant_new ("()"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + 10000, + NULL, + (GAsyncReadyCallback) bluez_disconnect_cb, + g_object_ref (self)); + + priv->connection_bt_type = NM_BT_CAPABILITY_NONE; +} + +static void +bluez_connect_cb (GDBusConnection *dbus_connection, + GAsyncResult *res, + gpointer user_data) +{ + GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data); + GObject *result_object = g_async_result_get_source_object (G_ASYNC_RESULT (result)); + NMBluezDevice *self = NM_BLUEZ_DEVICE (result_object); + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GError *error = NULL; + char *device; + GVariant *variant; + + variant = g_dbus_connection_call_finish (dbus_connection, res, &error); + + if (!variant) { + g_simple_async_result_take_error (result, error); + } else { + g_variant_get (variant, "(s)", &device); + + g_simple_async_result_set_op_res_gpointer (result, + g_strdup (device), + g_free); + priv->bt_iface = device; + g_variant_unref (variant); + } + + g_simple_async_result_complete (result); + g_object_unref (result); + g_object_unref (result_object); +} + +void +nm_bluez_device_connect_async (NMBluezDevice *self, + NMBluetoothCapabilities connection_bt_type, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + const char *dbus_iface; + const char *connect_type = BLUETOOTH_CONNECT_NAP; + + g_return_if_fail (priv->capabilities & connection_bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP)); + + if (priv->bluez_version == 5) { + g_return_if_fail (connection_bt_type == NM_BT_CAPABILITY_NAP); + dbus_iface = BLUEZ5_NETWORK_INTERFACE; + } else if (priv->bluez_version == 4 && connection_bt_type == NM_BT_CAPABILITY_DUN) { + dbus_iface = BLUEZ4_SERIAL_INTERFACE; + connect_type = BLUETOOTH_CONNECT_DUN; + } else { + g_return_if_fail (priv->bluez_version == 4 && connection_bt_type == NM_BT_CAPABILITY_NAP); + dbus_iface = BLUEZ4_NETWORK_INTERFACE; + } + + simple = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + nm_bluez_device_connect_async); + + g_dbus_connection_call (priv->dbus_connection, + BLUEZ_SERVICE, + priv->path, + dbus_iface, + "Connect", + g_variant_new ("(s)", connect_type), + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + NULL, + (GAsyncReadyCallback) bluez_connect_cb, + simple); + + priv->connection_bt_type = connection_bt_type; +} + +const char * +nm_bluez_device_connect_finish (NMBluezDevice *self, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + const char *device; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, + G_OBJECT (self), + nm_bluez_device_connect_async), + NULL); + + simple = (GSimpleAsyncResult *) result; + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + device = (const char *) g_simple_async_result_get_op_res_gpointer (simple); + return device; +} + +/***********************************************************/ + +static guint32 +convert_uuids_to_capabilities (const char **strings, int bluez_version) +{ + const char **iter; + guint32 capabilities = 0; + + for (iter = strings; iter && *iter; iter++) { + char **parts; + + parts = g_strsplit (*iter, "-", -1); + if (parts && parts[0]) { + switch (g_ascii_strtoull (parts[0], NULL, 16)) { + case 0x1103: + if (bluez_version == 4) + capabilities |= NM_BT_CAPABILITY_DUN; + break; + case 0x1116: + capabilities |= NM_BT_CAPABILITY_NAP; + break; + default: + break; + } + } + g_strfreev (parts); + } + + return capabilities; +} + +static void +_set_property_capabilities (NMBluezDevice *self, const char **uuids) +{ + guint32 uint_val; + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + + uint_val = convert_uuids_to_capabilities (uuids, priv->bluez_version); + if (priv->capabilities != uint_val) { + if (priv->capabilities) { + /* changing (relevant) capabilities is not supported and ignored -- except setting initially */ + nm_log_warn (LOGD_BT, "bluez[%s] ignore change of capabilities for Bluetooth device from %u to %u", + priv->path, priv->capabilities, uint_val); + return; + } + nm_log_dbg (LOGD_BT, "bluez[%s] set capabilities for Bluetooth device: %s%s%s", priv->path, + uint_val & NM_BT_CAPABILITY_NAP ? "NAP" : "", + ((uint_val & NM_BT_CAPABILITY_DUN) && (uint_val &NM_BT_CAPABILITY_NAP)) ? " | " : "", + uint_val & NM_BT_CAPABILITY_DUN ? "DUN" : ""); + priv->capabilities = uint_val; + g_object_notify (G_OBJECT (self), NM_BLUEZ_DEVICE_CAPABILITIES); + } +} + +/** + * priv->address can only be set one to a certain (non NULL) value. Every later attempt + * to reset it to another value will be ignored and a warning will be logged. + * + * When setting the address for the first time, we also set bin_address. + **/ +static void +_set_property_address (NMBluezDevice *self, const char *addr) +{ + struct ether_addr *tmp; + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + + if (g_strcmp0 (priv->address, addr) == 0) + return; + + if (!addr) { + nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from '%s' to NULL", priv->path, priv->address); + return; + } + + if (priv->address != NULL) { + nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from '%s' to '%s'", priv->path, priv->address, addr); + return; + } + + tmp = ether_aton (addr); + if (!tmp) { + if (priv->address) + nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from '%s' to '%s' (invalid value)", priv->path, priv->address, addr); + else + nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from NULL to '%s' (invalid value)", priv->path, addr); + return; + } + memcpy (priv->bin_address, tmp->ether_addr_octet, ETH_ALEN); + priv->address = g_strdup (addr); + g_object_notify (G_OBJECT (self), NM_BLUEZ_DEVICE_ADDRESS); +} + +static void +_take_variant_property_address (NMBluezDevice *self, GVariant *v) +{ + _set_property_address (self, VARIANT_IS_OF_TYPE_STRING (v) ? g_variant_get_string (v, NULL) : NULL); + if (v) + g_variant_unref (v); +} + +static void +_take_variant_property_name (NMBluezDevice *self, GVariant *v) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + const char *str; + + if (VARIANT_IS_OF_TYPE_STRING (v)) { + str = g_variant_get_string (v, NULL); + if (g_strcmp0 (priv->name, str)) { + g_free (priv->name); + priv->name = g_strdup (str); + g_object_notify (G_OBJECT (self), NM_BLUEZ_DEVICE_NAME); + } + } + if (v) + g_variant_unref (v); +} + +static void +_take_variant_property_uuids (NMBluezDevice *self, GVariant *v) +{ + if (VARIANT_IS_OF_TYPE_STRING_ARRAY (v)) { + const char **uuids = g_variant_get_strv (v, NULL); + + _set_property_capabilities (self, uuids); + g_free (uuids); + } + if (v) + g_variant_unref (v); +} + +static void +_take_variant_property_connected (NMBluezDevice *self, GVariant *v) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + + if (VARIANT_IS_OF_TYPE_BOOLEAN (v)) { + gboolean connected = g_variant_get_boolean (v); + + if (priv->connected != connected) { + priv->connected = connected; + g_object_notify (G_OBJECT (self), NM_BLUEZ_DEVICE_CONNECTED); + } + } + if (v) + g_variant_unref (v); +} + + +static void +adapter5_on_properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + gpointer user_data) +{ + NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data); + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GVariantIter i; + const char *property; + GVariant *v; + + g_variant_iter_init (&i, changed_properties); + while (g_variant_iter_next (&i, "{&sv}", &property, &v)) { + if (!strcmp (property, "Powered") && VARIANT_IS_OF_TYPE_BOOLEAN (v)) { + gboolean powered = g_variant_get_boolean (v); + if (priv->adapter_powered != powered) + priv->adapter_powered = powered; + } + g_variant_unref (v); + } + + check_emit_usable (self); +} + +static void +adapter5_on_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GError *error; + GVariant *v; + + priv->adapter5 = g_dbus_proxy_new_for_bus_finish (res, &error); + if (!priv->adapter5) { + nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire adapter proxy: %s.", priv->path, error->message); + g_clear_error (&error); + g_signal_emit (self, signals[INITIALIZED], 0, FALSE); + } else { + g_signal_connect (priv->adapter5, "g-properties-changed", + G_CALLBACK (adapter5_on_properties_changed), self); + + /* Check adapter's powered state */ + v = g_dbus_proxy_get_cached_property (priv->adapter5, "Powered"); + priv->adapter_powered = VARIANT_IS_OF_TYPE_BOOLEAN (v) ? g_variant_get_boolean (v) : FALSE; + if (v) + g_variant_unref (v); + + priv->initialized = TRUE; + g_signal_emit (self, signals[INITIALIZED], 0, TRUE); + + check_emit_usable (self); + } + + g_object_unref (self); +} + +static void +_take_one_variant_property (NMBluezDevice *self, const char *property, GVariant *v) +{ + if (v) { + if (!g_strcmp0 (property, "Address")) + _take_variant_property_address (self, v); + else if (!g_strcmp0 (property, "Connected")) + _take_variant_property_connected (self, v); + else if (!g_strcmp0 (property, "Name")) + _take_variant_property_name (self, v); + else if (!g_strcmp0 (property, "UUIDs")) + _take_variant_property_uuids (self, v); + else + g_variant_unref (v); + } +} + +static void +_set_properties (NMBluezDevice *self, GVariant *properties) +{ + GVariantIter i; + const char *property; + GVariant *v; + + g_object_freeze_notify (G_OBJECT (self)); + g_variant_iter_init (&i, properties); + while (g_variant_iter_next (&i, "{&sv}", &property, &v)) + _take_one_variant_property (self, property, v); + g_object_thaw_notify (G_OBJECT (self)); +} + +static void +properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + gpointer user_data) +{ + NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data); + + _set_properties (self, changed_properties); + check_emit_usable (self); +} + +static void +bluez4_property_changed (GDBusProxy *proxy, + const char *sender, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data); + + if (g_strcmp0 (signal_name, "PropertyChanged") == 0) { + const char *property = NULL; + GVariant *v = NULL; + + g_variant_get (parameters, "(&sv)", &property, &v); + _take_one_variant_property (self, property, v); + check_emit_usable (self); + } +} + +static void +get_properties_cb_4 (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data); + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GError *err = NULL; + GVariant *v_properties, *v_dict; + GVariantType *v_type; + + v_properties = g_dbus_proxy_call_finish (priv->proxy, res, &err); + if (!v_properties) { + nm_log_warn (LOGD_BT, "bluez[%s] error getting device properties: %s", + priv->path, err && err->message ? err->message : "(unknown)"); + g_error_free (err); + g_signal_emit (self, signals[INITIALIZED], 0, FALSE); + goto END; + } + + v_type = g_variant_type_new ("(a{sv})"); + if (g_variant_is_of_type (v_properties, v_type)) { + v_dict = g_variant_get_child_value (v_properties, 0); + _set_properties (self, v_dict); + g_variant_unref (v_dict); + } else { + nm_log_warn (LOGD_BT, "bluez[%s] GetProperties returns unexpected result of type %s", priv->path, g_variant_get_type_string (v_properties)); + } + g_variant_type_free (v_type); + + g_variant_unref (v_properties); + + /* Check if any connections match this device */ + load_connections (self); + + priv->initialized = TRUE; + g_signal_emit (self, signals[INITIALIZED], 0, TRUE); + + + check_emit_usable (self); + +END: + g_object_unref (self); +} + +static void +query_properties (NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GVariant *v; + + switch (priv->bluez_version) { + case 4: + g_dbus_proxy_call (priv->proxy, "GetProperties", NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, 3000, + NULL, get_properties_cb_4, g_object_ref (self)); + break; + case 5: + g_object_freeze_notify (G_OBJECT (self)); + _take_variant_property_address (self, g_dbus_proxy_get_cached_property (priv->proxy, "Address")); + _take_variant_property_connected (self, g_dbus_proxy_get_cached_property (priv->proxy, "Connected")); + _take_variant_property_name (self, g_dbus_proxy_get_cached_property (priv->proxy, "Name")); + _take_variant_property_uuids (self, g_dbus_proxy_get_cached_property (priv->proxy, "UUIDs")); + g_object_thaw_notify (G_OBJECT (self)); + + v = g_dbus_proxy_get_cached_property (priv->proxy, "Adapter"); + if (VARIANT_IS_OF_TYPE_OBJECT_PATH (v)) { + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + BLUEZ_SERVICE, + g_variant_get_string (v, NULL), + BLUEZ5_ADAPTER_INTERFACE, + NULL, + (GAsyncReadyCallback) adapter5_on_acquired, + g_object_ref (self)); + g_variant_unref (v); + } else { + /* If the Adapter property is unset at this point, we won't try to acquire the adapter later on + * and the device stays unusable. This should not happen, but if it does, log a debug message. */ + nm_log_dbg (LOGD_BT, "bluez[%s] device has no adapter property and cannot be used.", priv->path); + } + + /* Check if any connections match this device */ + load_connections (self); + + break; + } +} + +static void +on_proxy_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GError *error = NULL; + + priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (!priv->proxy) { + nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire device proxy: %s.", priv->path, error->message); + g_clear_error (&error); + g_signal_emit (self, signals[INITIALIZED], 0, FALSE); + } else { + g_signal_connect (priv->proxy, "g-properties-changed", + G_CALLBACK (properties_changed), self); + if (priv->bluez_version == 4) { + /* Watch for custom Bluez4 PropertyChanged signals */ + g_signal_connect (priv->proxy, "g-signal", + G_CALLBACK (bluez4_property_changed), self); + } + + query_properties (self); + } + g_object_unref (self); +} + +static void +on_bus_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + GError *error = NULL; + + priv->dbus_connection = g_bus_get_finish (res, &error); + + if (!priv->dbus_connection) { + nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire bus connection: %s.", priv->path, error->message); + g_clear_error (&error); + g_signal_emit (self, signals[INITIALIZED], 0, FALSE); + } else + check_emit_usable (self); + + g_object_unref (self); +} + +/********************************************************************/ + +NMBluezDevice * +nm_bluez_device_new (const char *path, NMConnectionProvider *provider, int bluez_version) +{ + NMBluezDevice *self; + NMBluezDevicePrivate *priv; + const char *interface_name = NULL; + + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (provider != NULL, NULL); + g_return_val_if_fail (bluez_version == 4 || bluez_version == 5, NULL); + + self = (NMBluezDevice *) g_object_new (NM_TYPE_BLUEZ_DEVICE, + NM_BLUEZ_DEVICE_PATH, path, + NULL); + if (!self) + return NULL; + + nm_log_dbg (LOGD_BT, "bluez[%s] create NMBluezDevice", path); + + priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + + priv->bluez_version = bluez_version; + + priv->provider = provider; + + g_signal_connect (priv->provider, + NM_CP_SIGNAL_CONNECTION_ADDED, + G_CALLBACK (cp_connection_added), + self); + + g_signal_connect (priv->provider, + NM_CP_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK (cp_connection_removed), + self); + + g_signal_connect (priv->provider, + NM_CP_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK (cp_connection_updated), + self); + + g_bus_get (G_BUS_TYPE_SYSTEM, + NULL, + (GAsyncReadyCallback) on_bus_acquired, + g_object_ref (self)); + + switch (priv->bluez_version) { + case 4: + interface_name = BLUEZ4_DEVICE_INTERFACE; + break; + case 5: + interface_name = BLUEZ5_DEVICE_INTERFACE; + break; + } + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + BLUEZ_SERVICE, + priv->path, + interface_name, + NULL, + (GAsyncReadyCallback) on_proxy_acquired, + g_object_ref (self)); + return self; +} + +static void +nm_bluez_device_init (NMBluezDevice *self) +{ +} + +static void +dispose (GObject *object) +{ + NMBluezDevice *self = NM_BLUEZ_DEVICE (object); + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); + NMConnection *to_delete = NULL; + + if (priv->pan_connection) { + /* Check whether we want to remove the created connection. If so, we take a reference + * and delete it at the end of dispose(). */ + if ( nm_settings_connection_get_unsaved (NM_SETTINGS_CONNECTION (priv->pan_connection)) + && nm_connection_compare (priv->pan_connection, priv->pan_connection_original, NM_SETTING_COMPARE_FLAG_EXACT)) + to_delete = g_object_ref (priv->pan_connection); + + priv->pan_connection = NULL; + g_clear_object (&priv->pan_connection_original); + } + + g_signal_handlers_disconnect_by_func (priv->provider, cp_connection_added, self); + g_signal_handlers_disconnect_by_func (priv->provider, cp_connection_removed, self); + g_signal_handlers_disconnect_by_func (priv->provider, cp_connection_updated, self); + + g_slist_free_full (priv->connections, g_object_unref); + priv->connections = NULL; + + g_clear_object (&priv->adapter5); + g_clear_object (&priv->dbus_connection); + + G_OBJECT_CLASS (nm_bluez_device_parent_class)->dispose (object); + + if (to_delete) { + nm_log_dbg (LOGD_BT, "bluez[%s] removing Bluetooth connection for NAP device: '%s' (%s)", priv->path, + nm_connection_get_id (to_delete), nm_connection_get_uuid (to_delete)); + nm_settings_connection_delete (NM_SETTINGS_CONNECTION (to_delete), NULL, NULL); + g_object_unref (to_delete); + } +} + +static void +finalize (GObject *object) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (object); + + nm_log_dbg (LOGD_BT, "bluez[%s]: finalize NMBluezDevice", priv->path); + + g_free (priv->path); + g_free (priv->address); + g_free (priv->name); + g_free (priv->bt_iface); + + if (priv->proxy) + g_signal_handlers_disconnect_by_data (priv->proxy, object); + g_clear_object (&priv->proxy); + + G_OBJECT_CLASS (nm_bluez_device_parent_class)->finalize (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string (value, priv->path); + break; + case PROP_ADDRESS: + g_value_set_string (value, priv->address); + break; + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + case PROP_CAPABILITIES: + g_value_set_uint (value, priv->capabilities); + break; + case PROP_USABLE: + g_value_set_boolean (value, priv->usable); + break; + case PROP_CONNECTED: + g_value_set_boolean (value, priv->connected); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_PATH: + /* construct only */ + priv->path = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_bluez_device_class_init (NMBluezDeviceClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (config_class); + + g_type_class_add_private (config_class, sizeof (NMBluezDevicePrivate)); + + /* virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_PATH, + g_param_spec_string (NM_BLUEZ_DEVICE_PATH, + "DBus Path", + "DBus Path", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_ADDRESS, + g_param_spec_string (NM_BLUEZ_DEVICE_ADDRESS, + "Address", + "Address", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_NAME, + g_param_spec_string (NM_BLUEZ_DEVICE_NAME, + "Name", + "Name", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_CAPABILITIES, + g_param_spec_uint (NM_BLUEZ_DEVICE_CAPABILITIES, + "Capabilities", + "Capabilities", + 0, G_MAXUINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_USABLE, + g_param_spec_boolean (NM_BLUEZ_DEVICE_USABLE, + "Usable", + "Usable", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_CONNECTED, + g_param_spec_boolean (NM_BLUEZ_DEVICE_CONNECTED, + "Connected", + "Connected", + FALSE, + G_PARAM_READABLE)); + + /* Signals */ + signals[INITIALIZED] = g_signal_new ("initialized", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMBluezDeviceClass, initialized), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + signals[REMOVED] = g_signal_new (NM_BLUEZ_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMBluezDeviceClass, removed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h new file mode 100644 index 000000000..0bf7d898b --- /dev/null +++ b/src/devices/bluetooth/nm-bluez-device.h @@ -0,0 +1,98 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2014 Red Hat, Inc. + */ + +#ifndef NM_BLUEZ_DEVICE_H +#define NM_BLUEZ_DEVICE_H + +#include <glib.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include <config.h> +#include "nm-connection.h" +#include "nm-connection-provider.h" + +#define NM_TYPE_BLUEZ_DEVICE (nm_bluez_device_get_type ()) +#define NM_BLUEZ_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDevice)) +#define NM_BLUEZ_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass)) +#define NM_IS_BLUEZ_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_DEVICE)) +#define NM_IS_BLUEZ_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ_DEVICE)) +#define NM_BLUEZ_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass)) + +/* Properties */ +#define NM_BLUEZ_DEVICE_PATH "path" +#define NM_BLUEZ_DEVICE_ADDRESS "address" +#define NM_BLUEZ_DEVICE_NAME "name" +#define NM_BLUEZ_DEVICE_CAPABILITIES "capabilities" +#define NM_BLUEZ_DEVICE_USABLE "usable" +#define NM_BLUEZ_DEVICE_CONNECTED "connected" + +/* Signals */ +#define NM_BLUEZ_DEVICE_REMOVED "removed" + +typedef struct { + GObject parent; +} NMBluezDevice; + +typedef struct { + GObjectClass parent; + + /* virtual functions */ + void (*initialized) (NMBluezDevice *self, gboolean success); + + void (*removed) (NMBluezDevice *self); +} NMBluezDeviceClass; + +GType nm_bluez_device_get_type (void); + +NMBluezDevice *nm_bluez_device_new (const char *path, NMConnectionProvider *provider, int bluez_version); + +const char *nm_bluez_device_get_path (NMBluezDevice *self); + +gboolean nm_bluez_device_get_initialized (NMBluezDevice *self); + +gboolean nm_bluez_device_get_usable (NMBluezDevice *self); + +const char *nm_bluez_device_get_address (NMBluezDevice *self); + +const char *nm_bluez_device_get_name (NMBluezDevice *self); + +guint32 nm_bluez_device_get_class (NMBluezDevice *self); + +guint32 nm_bluez_device_get_capabilities (NMBluezDevice *self); + +gboolean nm_bluez_device_get_connected (NMBluezDevice *self); + +void +nm_bluez_device_connect_async (NMBluezDevice *self, + NMBluetoothCapabilities connection_bt_type, + GAsyncReadyCallback callback, + gpointer user_data); + +const char * +nm_bluez_device_connect_finish (NMBluezDevice *self, + GAsyncResult *result, + GError **error); + +void +nm_bluez_device_disconnect (NMBluezDevice *self); + +#endif /* NM_BLUEZ_DEVICE_H */ + diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c new file mode 100644 index 000000000..04ffb0a47 --- /dev/null +++ b/src/devices/bluetooth/nm-bluez-manager.c @@ -0,0 +1,428 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2013 - 2014 Red Hat, Inc. + */ + +#include <signal.h> +#include <string.h> +#include <stdlib.h> +#include <gmodule.h> +#include <gio/gio.h> + +#include "nm-logging.h" +#include "nm-bluez-manager.h" +#include "nm-device-factory.h" +#include "nm-bluez4-manager.h" +#include "nm-bluez5-manager.h" +#include "nm-bluez-device.h" +#include "nm-bluez-common.h" +#include "nm-connection-provider.h" +#include "nm-device-bt.h" + +#include "nm-dbus-manager.h" + +typedef struct { + int bluez_version; + + NMConnectionProvider *provider; + NMBluez4Manager *manager4; + NMBluez5Manager *manager5; + + guint watch_name_id; + + GDBusProxy *introspect_proxy; + GCancellable *async_cancellable; +} NMBluezManagerPrivate; + +#define NM_BLUEZ_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_BLUEZ_MANAGER, NMBluezManagerPrivate)) + +static GType nm_bluez_manager_get_type (void); + +static void device_factory_interface_init (NMDeviceFactory *factory_iface); + +G_DEFINE_TYPE_EXTENDED (NMBluezManager, nm_bluez_manager, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_DEVICE_FACTORY, device_factory_interface_init)) + +static void check_bluez_and_try_setup (NMBluezManager *self); + +/**************************************************************************/ + +#define PLUGIN_TYPE NM_DEVICE_TYPE_BT + +G_MODULE_EXPORT NMDeviceFactory * +nm_device_factory_create (GError **error) +{ + return (NMDeviceFactory *) g_object_new (NM_TYPE_BLUEZ_MANAGER, NULL); +} + +G_MODULE_EXPORT NMDeviceType +nm_device_factory_get_device_type (void) +{ + return PLUGIN_TYPE; +} + +/************************************************************************/ + +struct AsyncData { + NMBluezManager *self; + GCancellable *async_cancellable; +}; + +static struct AsyncData * +async_data_pack (NMBluezManager *self) +{ + struct AsyncData *data = g_new (struct AsyncData, 1); + + data->self = self; + data->async_cancellable = g_object_ref (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->async_cancellable); + return data; +} + +static NMBluezManager * +async_data_unpack (struct AsyncData *async_data) +{ + NMBluezManager *self = g_cancellable_is_cancelled (async_data->async_cancellable) + ? NULL : async_data->self; + + g_object_unref (async_data->async_cancellable); + g_free (async_data); + return self; +} + + +/** + * Cancel any current attempt to detect the version and cleanup + * the related fields. + **/ +static void +cleanup_checking (NMBluezManager *self, gboolean do_unwatch_name) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + if (priv->async_cancellable) { + g_cancellable_cancel (priv->async_cancellable); + g_clear_object (&priv->async_cancellable); + } + + g_clear_object (&priv->introspect_proxy); + + if (do_unwatch_name && priv->watch_name_id) { + g_bus_unwatch_name (priv->watch_name_id); + priv->watch_name_id = 0; + } +} + + +static void +manager_bdaddr_added_cb (NMBluez4Manager *bluez_mgr, + NMBluezDevice *bt_device, + const char *bdaddr, + const char *name, + const char *object_path, + guint32 capabilities, + gpointer user_data) +{ + NMBluezManager *self = NM_BLUEZ_MANAGER (user_data); + NMDevice *device; + gboolean has_dun = (capabilities & NM_BT_CAPABILITY_DUN); + gboolean has_nap = (capabilities & NM_BT_CAPABILITY_NAP); + + g_return_if_fail (bdaddr != NULL); + g_return_if_fail (name != NULL); + g_return_if_fail (object_path != NULL); + g_return_if_fail (capabilities != NM_BT_CAPABILITY_NONE); + g_return_if_fail (NM_IS_BLUEZ_DEVICE (bt_device)); + + device = nm_device_bt_new (bt_device, object_path, bdaddr, name, capabilities); + if (!device) + return; + + nm_log_info (LOGD_BT, "BT device %s (%s) added (%s%s%s)", + name, + bdaddr, + has_dun ? "DUN" : "", + has_dun && has_nap ? " " : "", + has_nap ? "NAP" : ""); + g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); + g_object_unref (device); +} + +static void +setup_version_number (NMBluezManager *self, int bluez_version) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (!priv->bluez_version); + + nm_log_info (LOGD_BT, "use BlueZ version %d", bluez_version); + + priv->bluez_version = bluez_version; + + /* Just detected the version. Cleanup the ongoing checking/detection. */ + cleanup_checking (self, TRUE); +} + +static void +setup_bluez4 (NMBluezManager *self) +{ + NMBluez4Manager *manager; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version); + + setup_version_number (self, 4); + priv->manager4 = manager = nm_bluez4_manager_new (priv->provider); + + g_signal_connect (manager, + NM_BLUEZ_MANAGER_BDADDR_ADDED, + G_CALLBACK (manager_bdaddr_added_cb), + self); + + nm_bluez4_manager_query_devices (manager); +} + +static void +setup_bluez5 (NMBluezManager *self) +{ + NMBluez5Manager *manager; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version); + + setup_version_number (self, 5); + priv->manager5 = manager = nm_bluez5_manager_new (priv->provider); + + g_signal_connect (manager, + NM_BLUEZ_MANAGER_BDADDR_ADDED, + G_CALLBACK (manager_bdaddr_added_cb), + self); + + nm_bluez5_manager_query_devices (manager); +} + + +static void +watch_name_on_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + check_bluez_and_try_setup (NM_BLUEZ_MANAGER (user_data)); +} + + +static void +check_bluez_and_try_setup_final_step (NMBluezManager *self, int bluez_version, const char *reason) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (!priv->bluez_version); + + switch (bluez_version) { + case 4: + setup_bluez4 (self); + break; + case 5: + setup_bluez5 (self); + break; + default: + nm_log_dbg (LOGD_BT, "detecting BlueZ version failed: %s", reason); + + /* cancel current attempts to detect the version. */ + cleanup_checking (self, FALSE); + if (!priv->watch_name_id) { + priv->watch_name_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, + BLUEZ_SERVICE, + G_BUS_NAME_WATCHER_FLAGS_NONE, + watch_name_on_appeared, + NULL, + self, + NULL); + } + break; + } +} + +static void +check_bluez_and_try_setup_do_introspect (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NMBluezManager *self = async_data_unpack (user_data); + NMBluezManagerPrivate *priv; + GError *error = NULL; + GVariant *result; + const char *xml_data; + int bluez_version = 0; + const char *reason = NULL; + + if (!self) + return; + + priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (priv->introspect_proxy); + g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable)); + g_return_if_fail (!priv->bluez_version); + + g_clear_object (&priv->async_cancellable); + + result = g_dbus_proxy_call_finish (priv->introspect_proxy, res, &error); + + if (!result) { + char *reason2 = g_strdup_printf ("introspect failed with %s", error->message); + check_bluez_and_try_setup_final_step (self, 0, reason2); + g_error_free (error); + g_free (reason2); + return; + } + + g_variant_get (result, "(&s)", &xml_data); + + /* might not be the best approach to detect the version, but it's good enough in practice. */ + if (strstr (xml_data, "org.freedesktop.DBus.ObjectManager")) + bluez_version = 5; + else if (strstr (xml_data, BLUEZ4_MANAGER_INTERFACE)) + bluez_version = 4; + else + reason = "unexpected introspect result"; + + g_variant_unref (result); + + check_bluez_and_try_setup_final_step (self, bluez_version, reason); +} + +static void +check_bluez_and_try_setup_on_new_proxy (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NMBluezManager *self = async_data_unpack (user_data); + NMBluezManagerPrivate *priv; + GError *error = NULL; + + if (!self) + return; + + priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (!priv->introspect_proxy); + g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable)); + g_return_if_fail (!priv->bluez_version); + + priv->introspect_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (!priv->introspect_proxy) { + char *reason = g_strdup_printf ("bluez error creating dbus proxy: %s", error->message); + check_bluez_and_try_setup_final_step (self, 0, reason); + g_error_free (error); + g_free (reason); + return; + } + + g_dbus_proxy_call (priv->introspect_proxy, + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 3000, + priv->async_cancellable, + check_bluez_and_try_setup_do_introspect, + async_data_pack (self)); +} + +static void +check_bluez_and_try_setup (NMBluezManager *self) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (!priv->bluez_version); + + /* there should be no ongoing detection. Anyway, cleanup_checking. */ + cleanup_checking (self, FALSE); + + priv->async_cancellable = g_cancellable_new (); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + BLUEZ_SERVICE, + "/", + DBUS_INTERFACE_INTROSPECTABLE, + priv->async_cancellable, + check_bluez_and_try_setup_on_new_proxy, + async_data_pack (self)); +} + +/*********************************************************************/ + +static void +dispose (GObject *object) +{ + NMBluezManager *self = NM_BLUEZ_MANAGER (object); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + if (priv->manager4) { + g_signal_handlers_disconnect_by_func (priv->manager4, manager_bdaddr_added_cb, self); + g_clear_object (&priv->manager4); + } + if (priv->manager5) { + g_signal_handlers_disconnect_by_func (priv->manager5, manager_bdaddr_added_cb, self); + g_clear_object (&priv->manager5); + } + + cleanup_checking (self, TRUE); + + priv->bluez_version = 0; +} + +static void +constructed (GObject *object) +{ + NMBluezManager *self = NM_BLUEZ_MANAGER (object); + + G_OBJECT_CLASS (nm_bluez_manager_parent_class)->constructed (object); + + check_bluez_and_try_setup (self); +} + +static void +nm_bluez_manager_init (NMBluezManager *self) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + priv->provider = nm_connection_provider_get (); + g_assert (priv->provider); +} + +static void +device_factory_interface_init (NMDeviceFactory *factory_iface) +{ +} + +static void +nm_bluez_manager_class_init (NMBluezManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMBluezManagerPrivate)); + + /* virtual methods */ + object_class->dispose = dispose; + object_class->constructed = constructed; +} + diff --git a/src/devices/bluetooth/nm-bluez-manager.h b/src/devices/bluetooth/nm-bluez-manager.h new file mode 100644 index 000000000..68d6dbe5e --- /dev/null +++ b/src/devices/bluetooth/nm-bluez-manager.h @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2014 Red Hat, Inc. + */ + +#ifndef NM_BLUEZ_MANAGER_H +#define NM_BLUEZ_MANAGER_H + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define NM_TYPE_BLUEZ_MANAGER (nm_bluez_manager_get_type ()) +#define NM_BLUEZ_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager)) + +#define NM_BLUEZ_MANAGER_BDADDR_ADDED "bdaddr-added" + +typedef struct { + GObject parent; +} NMBluezManager; + +typedef struct { + GObjectClass parent; +} NMBluezManagerClass; + +#endif /* NM_BLUEZ_MANAGER_H */ + diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c new file mode 100644 index 000000000..ad1786f02 --- /dev/null +++ b/src/devices/bluetooth/nm-bluez4-adapter.c @@ -0,0 +1,413 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2012 Red Hat, Inc. + */ + +#include <glib.h> +#include <string.h> + +#include "NetworkManager.h" +#include "nm-dbus-manager.h" +#include "nm-bluez4-adapter.h" +#include "nm-bluez-device.h" +#include "nm-bluez-common.h" +#include "nm-dbus-glib-types.h" +#include "nm-logging.h" + + +G_DEFINE_TYPE (NMBluez4Adapter, nm_bluez4_adapter, G_TYPE_OBJECT) + +#define NM_BLUEZ4_ADAPTER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterPrivate)) + +typedef struct { + char *path; + DBusGProxy *proxy; + gboolean initialized; + + char *address; + GHashTable *devices; + + /* Cached for devices */ + NMConnectionProvider *provider; +} NMBluez4AdapterPrivate; + + +enum { + PROP_0, + PROP_PATH, + PROP_ADDRESS, + + LAST_PROP +}; + +/* Signals */ +enum { + INITIALIZED, + DEVICE_ADDED, + DEVICE_REMOVED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +static void device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device); + +const char * +nm_bluez4_adapter_get_path (NMBluez4Adapter *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), NULL); + + return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->path; +} + +const char * +nm_bluez4_adapter_get_address (NMBluez4Adapter *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), NULL); + + return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->address; +} + +gboolean +nm_bluez4_adapter_get_initialized (NMBluez4Adapter *self) +{ + g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), FALSE); + + return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->initialized; +} + +GSList * +nm_bluez4_adapter_get_devices (NMBluez4Adapter *self) +{ + GSList *devices = NULL; + GHashTableIter iter; + NMBluezDevice *device; + + g_hash_table_iter_init (&iter, NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->devices); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) { + if (nm_bluez_device_get_usable (device)) + devices = g_slist_append (devices, device); + } + return devices; +} + +static void +emit_device_removed (NMBluez4Adapter *self, NMBluezDevice *device) +{ + nm_log_dbg (LOGD_BT, "(%s): bluez device now unusable", + nm_bluez_device_get_path (device)); + g_signal_emit (self, signals[DEVICE_REMOVED], 0, device); +} + +static void +device_usable (NMBluezDevice *device, GParamSpec *pspec, gpointer user_data) +{ + NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data); + + if (nm_bluez_device_get_usable (device)) { + nm_log_dbg (LOGD_BT, "(%s): bluez device now usable (device address is %s)", + nm_bluez_device_get_path (device), + nm_bluez_device_get_address (device)); + g_signal_emit (self, signals[DEVICE_ADDED], 0, device); + } else + emit_device_removed (self, device); +} + +static void +device_initialized (NMBluezDevice *device, gboolean success, gpointer user_data) +{ + NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data); + + nm_log_dbg (LOGD_BT, "(%s): bluez device %s", + nm_bluez_device_get_path (device), + success ? "initialized" : "failed to initialize"); + if (!success) + device_do_remove (self, device); +} + +static void +device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device) +{ + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + + if (g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device))) { + g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_initialized), self); + g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_usable), self); + + if (nm_bluez_device_get_usable (device)) + emit_device_removed (self, device); + + g_object_unref (device); + } +} + +static void +device_created (DBusGProxy *proxy, const char *path, gpointer user_data) +{ + NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data); + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + NMBluezDevice *device; + + device = nm_bluez_device_new (path, priv->provider, 4); + g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self); + g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self); + g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device); + + nm_log_dbg (LOGD_BT, "(%s): new bluez device found", path); +} + +static void +device_removed (DBusGProxy *proxy, const char *path, gpointer user_data) +{ + NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data); + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + NMBluezDevice *device; + + nm_log_dbg (LOGD_BT, "(%s): bluez device removed", path); + + device = g_hash_table_lookup (priv->devices, path); + if (device) + device_do_remove (self, device); +} + + +static void +get_properties_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) +{ + NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data); + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + GHashTable *properties = NULL; + GError *err = NULL; + GValue *value; + GPtrArray *devices; + int i; + + if (!dbus_g_proxy_end_call (proxy, call, &err, + DBUS_TYPE_G_MAP_OF_VARIANT, &properties, + G_TYPE_INVALID)) { + nm_log_warn (LOGD_BT, "bluez error getting adapter properties: %s", + err && err->message ? err->message : "(unknown)"); + g_error_free (err); + goto done; + } + + value = g_hash_table_lookup (properties, "Address"); + priv->address = value ? g_value_dup_string (value) : NULL; + + value = g_hash_table_lookup (properties, "Devices"); + devices = value ? g_value_get_boxed (value) : NULL; + + for (i = 0; devices && i < devices->len; i++) + device_created (priv->proxy, g_ptr_array_index (devices, i), self); + + g_hash_table_unref (properties); + + priv->initialized = TRUE; + +done: + g_signal_emit (self, signals[INITIALIZED], 0, priv->initialized); +} + +static void +query_properties (NMBluez4Adapter *self) +{ + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + DBusGProxyCall *call; + + call = dbus_g_proxy_begin_call (priv->proxy, "GetProperties", + get_properties_cb, + self, + NULL, G_TYPE_INVALID); + if (!call) { + nm_log_warn (LOGD_BT, "failed to request Bluetooth adapter properties for %s.", + priv->path); + } +} + +/***********************************************************/ + +NMBluez4Adapter * +nm_bluez4_adapter_new (const char *path, NMConnectionProvider *provider) +{ + NMBluez4Adapter *self; + NMBluez4AdapterPrivate *priv; + DBusGConnection *connection; + + self = (NMBluez4Adapter *) g_object_new (NM_TYPE_BLUEZ4_ADAPTER, + NM_BLUEZ4_ADAPTER_PATH, path, + NULL); + if (!self) + return NULL; + + priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + + priv->provider = provider; + + connection = nm_dbus_manager_get_connection (nm_dbus_manager_get ()); + + priv->proxy = dbus_g_proxy_new_for_name (connection, + BLUEZ_SERVICE, + priv->path, + BLUEZ4_ADAPTER_INTERFACE); + + dbus_g_proxy_add_signal (priv->proxy, "DeviceCreated", + DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->proxy, "DeviceCreated", + G_CALLBACK (device_created), self, NULL); + + dbus_g_proxy_add_signal (priv->proxy, "DeviceRemoved", + DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->proxy, "DeviceRemoved", + G_CALLBACK (device_removed), self, NULL); + + query_properties (self); + return self; +} + +static void +nm_bluez4_adapter_init (NMBluez4Adapter *self) +{ + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + + priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, NULL); +} + +static gboolean +_find_all (gpointer key, gpointer value, gpointer user_data) +{ + return TRUE; +} + +static void +dispose (GObject *object) +{ + NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (object); + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self); + NMBluezDevice *device; + + while ((device = g_hash_table_find (priv->devices, _find_all, NULL))) + device_do_remove (self, device); + + G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (object); + + g_hash_table_destroy (priv->devices); + g_free (priv->address); + g_free (priv->path); + g_object_unref (priv->proxy); + + G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->finalize (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string (value, priv->path); + break; + case PROP_ADDRESS: + g_value_set_string (value, priv->address); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_PATH: + /* construct only */ + priv->path = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (config_class); + + g_type_class_add_private (config_class, sizeof (NMBluez4AdapterPrivate)); + + /* virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_PATH, + g_param_spec_string (NM_BLUEZ4_ADAPTER_PATH, + "DBus Path", + "DBus Path", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_ADDRESS, + g_param_spec_string (NM_BLUEZ4_ADAPTER_ADDRESS, + "Address", + "Address", + NULL, + G_PARAM_READABLE)); + + /* Signals */ + signals[INITIALIZED] = g_signal_new ("initialized", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMBluez4AdapterClass, initialized), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + signals[DEVICE_ADDED] = g_signal_new ("device-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMBluez4AdapterClass, device_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + + signals[DEVICE_REMOVED] = g_signal_new ("device-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMBluez4AdapterClass, device_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); +} + diff --git a/src/devices/bluetooth/nm-bluez4-adapter.h b/src/devices/bluetooth/nm-bluez4-adapter.h new file mode 100644 index 000000000..454ca557e --- /dev/null +++ b/src/devices/bluetooth/nm-bluez4-adapter.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2012 Red Hat, Inc. + */ + +#ifndef NM_BLUEZ4_ADAPTER_H +#define NM_BLUEZ4_ADAPTER_H + +#include <glib.h> +#include <glib-object.h> + +#include "nm-bluez-device.h" +#include "nm-connection-provider.h" + +#define NM_TYPE_BLUEZ4_ADAPTER (nm_bluez4_adapter_get_type ()) +#define NM_BLUEZ4_ADAPTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4Adapter)) +#define NM_BLUEZ4_ADAPTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass)) +#define NM_IS_BLUEZ4_ADAPTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ4_ADAPTER)) +#define NM_IS_BLUEZ4_ADAPTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_ADAPTER)) +#define NM_BLUEZ4_ADAPTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass)) + +#define NM_BLUEZ4_ADAPTER_PATH "path" +#define NM_BLUEZ4_ADAPTER_ADDRESS "address" + +typedef struct { + GObject parent; +} NMBluez4Adapter; + +typedef struct { + GObjectClass parent; + + /* virtual functions */ + void (*initialized) (NMBluez4Adapter *self, gboolean success); + + void (*device_added) (NMBluez4Adapter *self, NMBluezDevice *device); + + void (*device_removed) (NMBluez4Adapter *self, NMBluezDevice *device); +} NMBluez4AdapterClass; + +GType nm_bluez4_adapter_get_type (void); + +NMBluez4Adapter *nm_bluez4_adapter_new (const char *path, + NMConnectionProvider *provider); + +const char *nm_bluez4_adapter_get_path (NMBluez4Adapter *self); + +const char *nm_bluez4_adapter_get_address (NMBluez4Adapter *self); + +gboolean nm_bluez4_adapter_get_initialized (NMBluez4Adapter *self); + +GSList *nm_bluez4_adapter_get_devices (NMBluez4Adapter *self); + +#endif /* NM_BLUEZ4_ADAPTER_H */ + diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c new file mode 100644 index 000000000..2660cbd92 --- /dev/null +++ b/src/devices/bluetooth/nm-bluez4-manager.c @@ -0,0 +1,361 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2013 Red Hat, Inc. + */ + +#include <signal.h> +#include <string.h> +#include <stdlib.h> +#include <dbus/dbus-glib.h> + +#include "nm-logging.h" +#include "nm-dbus-glib-types.h" +#include "nm-bluez-manager.h" +#include "nm-bluez4-manager.h" +#include "nm-bluez4-adapter.h" +#include "nm-dbus-manager.h" +#include "nm-bluez-common.h" + + +typedef struct { + NMDBusManager *dbus_mgr; + gulong name_owner_changed_id; + + NMConnectionProvider *provider; + + DBusGProxy *proxy; + + NMBluez4Adapter *adapter; +} NMBluez4ManagerPrivate; + +#define NM_BLUEZ4_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_BLUEZ4_MANAGER, NMBluez4ManagerPrivate)) + +G_DEFINE_TYPE (NMBluez4Manager, nm_bluez4_manager, G_TYPE_OBJECT) + +enum { + BDADDR_ADDED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void + +emit_bdaddr_added (NMBluez4Manager *self, NMBluezDevice *device) +{ + g_signal_emit (self, signals[BDADDR_ADDED], 0, + device, + nm_bluez_device_get_address (device), + nm_bluez_device_get_name (device), + nm_bluez_device_get_path (device), + nm_bluez_device_get_capabilities (device)); +} + +void +nm_bluez4_manager_query_devices (NMBluez4Manager *self) +{ + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + GSList *devices, *iter; + + if (!priv->adapter) + return; + + devices = nm_bluez4_adapter_get_devices (priv->adapter); + for (iter = devices; iter; iter = g_slist_next (iter)) + emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data)); + g_slist_free (devices); +} + +static void +device_added (NMBluez4Adapter *adapter, NMBluezDevice *device, gpointer user_data) +{ + emit_bdaddr_added (NM_BLUEZ4_MANAGER (user_data), device); +} + +static void +device_removed (NMBluez4Adapter *adapter, NMBluezDevice *device, gpointer user_data) +{ + /* Re-emit the signal on the device for now; flatten this later */ + g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED); +} + +static void +adapter_initialized (NMBluez4Adapter *adapter, gboolean success, gpointer user_data) +{ + NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data); + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + + if (success) { + GSList *devices, *iter; + + devices = nm_bluez4_adapter_get_devices (adapter); + for (iter = devices; iter; iter = g_slist_next (iter)) + emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data)); + g_slist_free (devices); + + g_signal_connect (adapter, "device-added", G_CALLBACK (device_added), self); + g_signal_connect (adapter, "device-removed", G_CALLBACK (device_removed), self); + } else { + g_object_unref (priv->adapter); + priv->adapter = NULL; + } +} + +static void +adapter_removed (DBusGProxy *proxy, const char *path, NMBluez4Manager *self) +{ + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + + if (priv->adapter && !strcmp (path, nm_bluez4_adapter_get_path (priv->adapter))) { + if (nm_bluez4_adapter_get_initialized (priv->adapter)) { + GSList *devices, *iter; + + devices = nm_bluez4_adapter_get_devices (priv->adapter); + for (iter = devices; iter; iter = g_slist_next (iter)) + g_signal_emit_by_name (NM_BLUEZ_DEVICE (iter->data), NM_BLUEZ_DEVICE_REMOVED); + g_slist_free (devices); + } + + g_object_unref (priv->adapter); + priv->adapter = NULL; + } +} + +static void +default_adapter_changed (DBusGProxy *proxy, const char *path, NMBluez4Manager *self) +{ + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + const char *cur_path = NULL; + + if (priv->adapter) + cur_path = nm_bluez4_adapter_get_path (priv->adapter); + + if (cur_path) { + if (!path || strcmp (path, cur_path)) { + /* Default adapter changed */ + adapter_removed (priv->proxy, cur_path, self); + } else { + /* This adapter is already the default */ + return; + } + } + + /* Add the new default adapter */ + if (path) { + priv->adapter = nm_bluez4_adapter_new (path, priv->provider); + g_signal_connect (priv->adapter, "initialized", G_CALLBACK (adapter_initialized), self); + } +} + +static void +default_adapter_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) +{ + NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data); + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + const char *default_adapter = NULL; + GError *err = NULL; + + if (!dbus_g_proxy_end_call (proxy, call, &err, + DBUS_TYPE_G_OBJECT_PATH, &default_adapter, + G_TYPE_INVALID)) { + /* Ignore "No such adapter" errors; just means bluetooth isn't active */ + if ( !dbus_g_error_has_name (err, "org.bluez.Error.NoSuchAdapter") + && !dbus_g_error_has_name (err, "org.freedesktop.systemd1.LoadFailed") + && !g_error_matches (err, DBUS_GERROR, DBUS_GERROR_SERVICE_UNKNOWN)) { + nm_log_warn (LOGD_BT, "bluez error getting default adapter: %s", + err && err->message ? err->message : "(unknown)"); + } + g_error_free (err); + return; + } + + default_adapter_changed (priv->proxy, default_adapter, self); +} + +static void +query_default_adapter (NMBluez4Manager *self) +{ + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + DBusGProxyCall *call; + + call = dbus_g_proxy_begin_call (priv->proxy, "DefaultAdapter", + default_adapter_cb, + self, + NULL, G_TYPE_INVALID); + if (!call) + nm_log_warn (LOGD_BT, "failed to request default Bluetooth adapter."); +} + +static void +bluez_connect (NMBluez4Manager *self) +{ + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + DBusGConnection *connection; + + g_return_if_fail (priv->proxy == NULL); + + connection = nm_dbus_manager_get_connection (priv->dbus_mgr); + if (!connection) + return; + + priv->proxy = dbus_g_proxy_new_for_name (connection, + BLUEZ_SERVICE, + BLUEZ_MANAGER_PATH, + BLUEZ4_MANAGER_INTERFACE); + + dbus_g_proxy_add_signal (priv->proxy, "AdapterRemoved", + DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->proxy, "AdapterRemoved", + G_CALLBACK (adapter_removed), self, NULL); + + dbus_g_proxy_add_signal (priv->proxy, "DefaultAdapterChanged", + DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->proxy, "DefaultAdapterChanged", + G_CALLBACK (default_adapter_changed), self, NULL); + + query_default_adapter (self); +} + +static void +name_owner_changed_cb (NMDBusManager *dbus_mgr, + const char *name, + const char *old_owner, + const char *new_owner, + gpointer user_data) +{ + NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data); + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + gboolean old_owner_good = (old_owner && strlen (old_owner)); + gboolean new_owner_good = (new_owner && strlen (new_owner)); + + /* Can't handle the signal if its not from the Bluez */ + if (strcmp (BLUEZ_SERVICE, name)) + return; + + if (!old_owner_good && new_owner_good) + query_default_adapter (self); + else if (old_owner_good && !new_owner_good) { + /* Throwing away the adapter removes all devices too */ + if (priv->adapter) { + g_object_unref (priv->adapter); + priv->adapter = NULL; + } + } +} + +static void +bluez_cleanup (NMBluez4Manager *self, gboolean do_signal) +{ + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + + if (priv->proxy) { + g_object_unref (priv->proxy); + priv->proxy = NULL; + } + + if (priv->adapter) { + g_object_unref (priv->adapter); + priv->adapter = NULL; + } +} + +static void +dbus_connection_changed_cb (NMDBusManager *dbus_mgr, + DBusGConnection *connection, + gpointer user_data) +{ + NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data); + + if (!connection) + bluez_cleanup (self, TRUE); + else + bluez_connect (self); +} + +/****************************************************************/ + +NMBluez4Manager * +nm_bluez4_manager_new (NMConnectionProvider *provider) +{ + NMBluez4Manager *instance; + + instance = g_object_new (NM_TYPE_BLUEZ4_MANAGER, NULL); + NM_BLUEZ4_MANAGER_GET_PRIVATE (instance)->provider = provider; + return instance; +} + +static void +nm_bluez4_manager_init (NMBluez4Manager *self) +{ + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + + priv->dbus_mgr = nm_dbus_manager_get (); + g_assert (priv->dbus_mgr); + + g_signal_connect (priv->dbus_mgr, + NM_DBUS_MANAGER_NAME_OWNER_CHANGED, + G_CALLBACK (name_owner_changed_cb), + self); + + g_signal_connect (priv->dbus_mgr, + NM_DBUS_MANAGER_DBUS_CONNECTION_CHANGED, + G_CALLBACK (dbus_connection_changed_cb), + self); + + bluez_connect (self); +} + +static void +dispose (GObject *object) +{ + NMBluez4Manager *self = NM_BLUEZ4_MANAGER (object); + NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self); + + bluez_cleanup (self, FALSE); + + if (priv->dbus_mgr) { + g_signal_handlers_disconnect_by_func (priv->dbus_mgr, name_owner_changed_cb, self); + g_signal_handlers_disconnect_by_func (priv->dbus_mgr, dbus_connection_changed_cb, self); + priv->dbus_mgr = NULL; + } + + G_OBJECT_CLASS (nm_bluez4_manager_parent_class)->dispose (object); +} + +static void +nm_bluez4_manager_class_init (NMBluez4ManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMBluez4ManagerPrivate)); + + /* virtual methods */ + object_class->dispose = dispose; + + /* Signals */ + signals[BDADDR_ADDED] = + g_signal_new (NM_BLUEZ_MANAGER_BDADDR_ADDED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMBluez4ManagerClass, bdaddr_added), + NULL, NULL, NULL, + G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT); +} + diff --git a/src/devices/bluetooth/nm-bluez4-manager.h b/src/devices/bluetooth/nm-bluez4-manager.h new file mode 100644 index 000000000..19b1c65a1 --- /dev/null +++ b/src/devices/bluetooth/nm-bluez4-manager.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2013 Red Hat, Inc. + */ + +#ifndef NM_BLUEZ4_MANAGER_H +#define NM_BLUEZ4_MANAGER_H + +#include <glib.h> +#include <glib-object.h> + +#include <config.h> +#include "nm-connection-provider.h" + +G_BEGIN_DECLS + +#define NM_TYPE_BLUEZ4_MANAGER (nm_bluez4_manager_get_type ()) +#define NM_BLUEZ4_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ4_MANAGER, NMBluez4Manager)) +#define NM_BLUEZ4_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ4_MANAGER, NMBluez4ManagerClass)) +#define NM_IS_BLUEZ4_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ4_MANAGER)) +#define NM_IS_BLUEZ4_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_MANAGER)) +#define NM_BLUEZ4_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_MANAGER, NMBluez4ManagerClass)) + +typedef struct { + GObject parent; +} NMBluez4Manager; + +typedef struct { + GObjectClass parent; + + /* Signals */ + void (*bdaddr_added) (NMBluez4Manager *manager, + const char *bdaddr, + const char *name, + const char *object_path, + guint uuids); +} NMBluez4ManagerClass; + +GType nm_bluez4_manager_get_type (void); + +NMBluez4Manager *nm_bluez4_manager_new (NMConnectionProvider *provider); + +void nm_bluez4_manager_query_devices (NMBluez4Manager *manager); + +#endif /* NM_BLUEZ4_MANAGER_H */ + diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c new file mode 100644 index 000000000..63006b3ab --- /dev/null +++ b/src/devices/bluetooth/nm-bluez5-manager.c @@ -0,0 +1,421 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2013 Intel Corporation. + */ + +#include <signal.h> +#include <string.h> +#include <stdlib.h> +#include <gio/gio.h> + +#include "nm-logging.h" +#include "nm-bluez-manager.h" +#include "nm-bluez5-manager.h" +#include "nm-bluez-device.h" +#include "nm-bluez-common.h" + +#include "nm-dbus-manager.h" + +typedef struct { + NMDBusManager *dbus_mgr; + gulong name_owner_changed_id; + + NMConnectionProvider *provider; + + GDBusProxy *proxy; + + GHashTable *devices; +} NMBluez5ManagerPrivate; + +#define NM_BLUEZ5_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerPrivate)) + +G_DEFINE_TYPE (NMBluez5Manager, nm_bluez5_manager, G_TYPE_OBJECT) + +enum { + BDADDR_ADDED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self); +static void device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self); + +static void +emit_bdaddr_added (NMBluez5Manager *self, NMBluezDevice *device) +{ + g_signal_emit (self, signals[BDADDR_ADDED], 0, + device, + nm_bluez_device_get_address (device), + nm_bluez_device_get_name (device), + nm_bluez_device_get_path (device), + nm_bluez_device_get_capabilities (device)); +} + +void +nm_bluez5_manager_query_devices (NMBluez5Manager *self) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + NMBluezDevice *device; + GHashTableIter iter; + + g_hash_table_iter_init (&iter, priv->devices); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) { + if (nm_bluez_device_get_usable (device)) + emit_bdaddr_added (self, device); + } +} + +static void +remove_device (NMBluez5Manager *self, NMBluezDevice *device) +{ + g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_initialized), self); + g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_usable), self); + if (nm_bluez_device_get_usable (device)) + g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED); +} + +static void +remove_all_devices (NMBluez5Manager *self) +{ + GHashTableIter iter; + NMBluezDevice *device; + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + + g_hash_table_iter_init (&iter, priv->devices); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) { + g_hash_table_iter_steal (&iter); + remove_device (self, device); + g_object_unref (device); + } +} + +static void +device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self) +{ + gboolean usable = nm_bluez_device_get_usable (device); + + nm_log_dbg (LOGD_BT, "(%s): bluez device now %s", + nm_bluez_device_get_path (device), + usable ? "usable" : "unusable"); + + if (usable) { + nm_log_dbg (LOGD_BT, "(%s): bluez device address %s", + nm_bluez_device_get_path (device), + nm_bluez_device_get_address (device)); + emit_bdaddr_added (self, device); + } else + g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED); +} + +static void +device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + + nm_log_dbg (LOGD_BT, "(%s): bluez device %s", + nm_bluez_device_get_path (device), + success ? "initialized" : "failed to initialize"); + if (!success) + g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device)); +} + +static void +device_added (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + NMBluezDevice *device; + + device = nm_bluez_device_new (path, priv->provider, 5); + g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self); + g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self); + g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device); + + nm_log_dbg (LOGD_BT, "(%s): new bluez device found", path); +} + +static void +device_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + NMBluezDevice *device; + + nm_log_dbg (LOGD_BT, "(%s): bluez device removed", path); + + device = g_hash_table_lookup (priv->devices, path); + if (device) { + g_hash_table_steal (priv->devices, nm_bluez_device_get_path (device)); + remove_device (NM_BLUEZ5_MANAGER (self), device); + g_object_unref (device); + } +} + +static void +object_manager_g_signal (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + NMBluez5Manager *self) +{ + GVariant *variant; + const gchar *path; + + if (!strcmp (signal_name, "InterfacesRemoved")) { + const gchar **ifaces; + gsize i, length; + + g_variant_get (parameters, "(&o*)", &path, &variant); + + ifaces = g_variant_get_strv (variant, &length); + + for (i = 0; i < length; i++) { + if (!strcmp (ifaces[i], BLUEZ5_DEVICE_INTERFACE)) { + device_removed (proxy, path, self); + break; + } + } + + g_free (ifaces); + + } else if (!strcmp (signal_name, "InterfacesAdded")) { + g_variant_get (parameters, "(&o*)", &path, &variant); + + if (g_variant_lookup_value (variant, BLUEZ5_DEVICE_INTERFACE, + G_VARIANT_TYPE_DICTIONARY)) + device_added (proxy, path, self); + } +} + +static void +get_managed_objects_cb (GDBusProxy *proxy, + GAsyncResult *res, + NMBluez5Manager *self) +{ + GVariant *variant, *ifaces; + GVariantIter i; + GError *error = NULL; + const char *path; + + variant = g_dbus_proxy_call_finish (proxy, res, &error); + + if (!variant) { + if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) + nm_log_warn (LOGD_BT, "Couldn't get managed objects: not running Bluez5?"); + else { + nm_log_warn (LOGD_BT, "Couldn't get managed objects: %s", + error && error->message ? error->message : "(unknown)"); + } + g_clear_error (&error); + return; + } + g_variant_iter_init (&i, g_variant_get_child_value (variant, 0)); + while ((g_variant_iter_next (&i, "{&o*}", &path, &ifaces))) { + if (g_variant_lookup_value (ifaces, BLUEZ5_DEVICE_INTERFACE, + G_VARIANT_TYPE_DICTIONARY)) { + device_added (proxy, path, self); + } + } + + g_variant_unref (variant); +} + +static void +on_proxy_acquired (GObject *object, + GAsyncResult *res, + NMBluez5Manager *self) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + GError *error = NULL; + + priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (!priv->proxy) { + nm_log_warn (LOGD_BT, "Couldn't acquire object manager proxy: %s", + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + return; + } + + /* Get already managed devices. */ + g_dbus_proxy_call (priv->proxy, "GetManagedObjects", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) get_managed_objects_cb, + self); + + g_signal_connect (priv->proxy, "g-signal", + G_CALLBACK (object_manager_g_signal), self); +} + +static void +bluez_connect (NMBluez5Manager *self) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + + g_return_if_fail (priv->proxy == NULL); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + BLUEZ_SERVICE, + BLUEZ_MANAGER_PATH, + OBJECT_MANAGER_INTERFACE, + NULL, + (GAsyncReadyCallback) on_proxy_acquired, + self); +} + +static void +name_owner_changed_cb (NMDBusManager *dbus_mgr, + const char *name, + const char *old_owner, + const char *new_owner, + gpointer user_data) +{ + NMBluez5Manager *self = NM_BLUEZ5_MANAGER (user_data); + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + gboolean old_owner_good = (old_owner && strlen (old_owner)); + gboolean new_owner_good = (new_owner && strlen (new_owner)); + + /* Can't handle the signal if its not from the Bluez */ + if (strcmp (BLUEZ_SERVICE, name)) + return; + + if (old_owner_good && !new_owner_good) { + if (priv->devices) + remove_all_devices (self); + } +} + +static void +bluez_cleanup (NMBluez5Manager *self, gboolean do_signal) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + + if (priv->proxy) { + g_object_unref (priv->proxy); + priv->proxy = NULL; + } + + if (do_signal) + remove_all_devices (self); + else + g_hash_table_remove_all (priv->devices); +} + +static void +dbus_connection_changed_cb (NMDBusManager *dbus_mgr, + DBusGConnection *connection, + gpointer user_data) +{ + NMBluez5Manager *self = NM_BLUEZ5_MANAGER (user_data); + + if (!connection) + bluez_cleanup (self, TRUE); + else + bluez_connect (self); +} + +/****************************************************************/ + +NMBluez5Manager * +nm_bluez5_manager_new (NMConnectionProvider *provider) +{ + NMBluez5Manager *instance = NULL; + + instance = g_object_new (NM_TYPE_BLUEZ5_MANAGER, NULL); + NM_BLUEZ5_MANAGER_GET_PRIVATE (instance)->provider = provider; + return instance; +} + +static void +nm_bluez5_manager_init (NMBluez5Manager *self) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + + priv->dbus_mgr = nm_dbus_manager_get (); + g_assert (priv->dbus_mgr); + + g_signal_connect (priv->dbus_mgr, + NM_DBUS_MANAGER_NAME_OWNER_CHANGED, + G_CALLBACK (name_owner_changed_cb), + self); + + g_signal_connect (priv->dbus_mgr, + NM_DBUS_MANAGER_DBUS_CONNECTION_CHANGED, + G_CALLBACK (dbus_connection_changed_cb), + self); + + bluez_connect (self); + + priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, g_object_unref); +} + +static void +dispose (GObject *object) +{ + NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object); + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); + + bluez_cleanup (self, FALSE); + + if (priv->dbus_mgr) { + g_signal_handlers_disconnect_by_func (priv->dbus_mgr, name_owner_changed_cb, self); + g_signal_handlers_disconnect_by_func (priv->dbus_mgr, dbus_connection_changed_cb, self); + priv->dbus_mgr = NULL; + } + + G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (object); + + g_hash_table_destroy (priv->devices); + + G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->finalize (object); +} + +static void +nm_bluez5_manager_class_init (NMBluez5ManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMBluez5ManagerPrivate)); + + /* virtual methods */ + object_class->dispose = dispose; + object_class->finalize = finalize; + + /* Signals */ + signals[BDADDR_ADDED] = + g_signal_new (NM_BLUEZ_MANAGER_BDADDR_ADDED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMBluez5ManagerClass, bdaddr_added), + NULL, NULL, NULL, + G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT); +} diff --git a/src/devices/bluetooth/nm-bluez5-manager.h b/src/devices/bluetooth/nm-bluez5-manager.h new file mode 100644 index 000000000..79f347bce --- /dev/null +++ b/src/devices/bluetooth/nm-bluez5-manager.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2013 Red Hat, Inc. + */ + +#ifndef NM_BLUEZ5_MANAGER_H +#define NM_BLUEZ5_MANAGER_H + +#include <glib.h> +#include <glib-object.h> + +#include <config.h> +#include "nm-connection-provider.h" + +G_BEGIN_DECLS + +#define NM_TYPE_BLUEZ5_MANAGER (nm_bluez5_manager_get_type ()) +#define NM_BLUEZ5_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5Manager)) +#define NM_BLUEZ5_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass)) +#define NM_IS_BLUEZ5_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ5_MANAGER)) +#define NM_IS_BLUEZ5_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ5_MANAGER)) +#define NM_BLUEZ5_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass)) + +typedef struct { + GObject parent; +} NMBluez5Manager; + +typedef struct { + GObjectClass parent; + + /* Signals */ + void (*bdaddr_added) (NMBluez5Manager *manager, + const char *bdaddr, + const char *name, + const char *object_path, + guint uuids); +} NMBluez5ManagerClass; + +GType nm_bluez5_manager_get_type (void); + +NMBluez5Manager *nm_bluez5_manager_new (NMConnectionProvider *provider); + +void nm_bluez5_manager_query_devices (NMBluez5Manager *manager); + +#endif /* NM_BLUEZ5_MANAGER_H */ + diff --git a/src/devices/bluetooth/nm-bt-enum-types.c b/src/devices/bluetooth/nm-bt-enum-types.c new file mode 100644 index 000000000..2595caeb7 --- /dev/null +++ b/src/devices/bluetooth/nm-bt-enum-types.c @@ -0,0 +1,32 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#include "nm-bt-enum-types.h" + +#include "nm-device-bt.h" + +GType +nm_bt_error_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { NM_BT_ERROR_CONNECTION_NOT_BT, "NM_BT_ERROR_CONNECTION_NOT_BT", "ConnectionNotBt" }, + { NM_BT_ERROR_CONNECTION_INVALID, "NM_BT_ERROR_CONNECTION_INVALID", "ConnectionInvalid" }, + { NM_BT_ERROR_CONNECTION_INCOMPATIBLE, "NM_BT_ERROR_CONNECTION_INCOMPATIBLE", "ConnectionIncompatible" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("NMBtError"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + + + diff --git a/src/devices/bluetooth/nm-bt-enum-types.h b/src/devices/bluetooth/nm-bt-enum-types.h new file mode 100644 index 000000000..af887929b --- /dev/null +++ b/src/devices/bluetooth/nm-bt-enum-types.h @@ -0,0 +1,19 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#ifndef __NM_BT_ENUM_TYPES_H__ +#define __NM_BT_ENUM_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS +GType nm_bt_error_get_type (void) G_GNUC_CONST; +#define NM_TYPE_BT_ERROR (nm_bt_error_get_type ()) +G_END_DECLS + +#endif /* __NM_BT_ENUM_TYPES_H__ */ + + + diff --git a/src/devices/bluetooth/nm-device-bt-glue.h b/src/devices/bluetooth/nm-device-bt-glue.h new file mode 100644 index 000000000..b6f851b25 --- /dev/null +++ b/src/devices/bluetooth/nm-device-bt-glue.h @@ -0,0 +1,73 @@ +/* Generated by dbus-binding-tool; do not edit! */ + + +#ifndef __dbus_glib_marshal_nm_device_bt_MARSHAL_H__ +#define __dbus_glib_marshal_nm_device_bt_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +G_END_DECLS + +#endif /* __dbus_glib_marshal_nm_device_bt_MARSHAL_H__ */ + +#include <dbus/dbus-glib.h> +static const DBusGMethodInfo dbus_glib_nm_device_bt_methods[] = { +}; + +const DBusGObjectInfo dbus_glib_nm_device_bt_object_info = { 1, + dbus_glib_nm_device_bt_methods, + 0, +"\0", +"org.freedesktop.NetworkManager.Device.Bluetooth\0PropertiesChanged\0\0", +"org.freedesktop.NetworkManager.Device.Bluetooth\0HwAddress\0hw_address\0read\0org.freedesktop.NetworkManager.Device.Bluetooth\0Name\0name\0read\0org.freedesktop.NetworkManager.Device.Bluetooth\0BtCapabilities\0bt_capabilities\0read\0\0" +}; + diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c new file mode 100644 index 000000000..0bd2f17d1 --- /dev/null +++ b/src/devices/bluetooth/nm-device-bt.c @@ -0,0 +1,1278 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2011 Red Hat, Inc. + */ + +#include "config.h" + +#include <stdio.h> +#include <string.h> +#include <net/ethernet.h> +#include <netinet/ether.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "nm-glib-compat.h" +#include "nm-bluez-common.h" +#include "nm-bluez-device.h" +#include "nm-dbus-manager.h" +#include "nm-device-bt.h" +#include "nm-device-private.h" +#include "nm-logging.h" +#include "ppp-manager/nm-ppp-manager.h" +#include "nm-setting-connection.h" +#include "nm-setting-bluetooth.h" +#include "nm-setting-cdma.h" +#include "nm-setting-gsm.h" +#include "nm-setting-serial.h" +#include "nm-setting-ppp.h" +#include "nm-device-bt-glue.h" +#include "NetworkManagerUtils.h" +#include "nm-bt-enum-types.h" +#include "nm-utils.h" + +#define MM_OLD_DBUS_SERVICE "org.freedesktop.ModemManager" +#define MM_NEW_DBUS_SERVICE "org.freedesktop.ModemManager1" + +G_DEFINE_TYPE (NMDeviceBt, nm_device_bt, NM_TYPE_DEVICE) + +#define NM_DEVICE_BT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_BT, NMDeviceBtPrivate)) + +static gboolean modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *reason); + +typedef struct { + NMDBusManager *dbus_mgr; + guint mm_watch_id; + gboolean mm_running; + + NMBluezDevice *bt_device; + + guint8 bdaddr[ETH_ALEN]; + char *name; + guint32 capabilities; + + gboolean connected; + gboolean have_iface; + + char *rfcomm_iface; + NMModem *modem; + guint32 timeout_id; + + guint32 bt_type; /* BT type of the current connection */ +} NMDeviceBtPrivate; + +enum { + PROP_0, + PROP_BT_NAME, + PROP_BT_CAPABILITIES, + PROP_BT_DEVICE, + + LAST_PROP +}; + +enum { + PPP_STATS, + + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + + +#define NM_BT_ERROR (nm_bt_error_quark ()) + +static GQuark +nm_bt_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-bt-error"); + return quark; +} + +guint32 nm_device_bt_get_capabilities (NMDeviceBt *self) +{ + g_return_val_if_fail (NM_IS_DEVICE_BT (self), NM_BT_CAPABILITY_NONE); + + return NM_DEVICE_BT_GET_PRIVATE (self)->capabilities; +} + +static guint +get_hw_address_length (NMDevice *device, gboolean *out_permanent) +{ + /* HW address is the Bluetooth HW address of the remote device */ + if (out_permanent) + *out_permanent = TRUE; /* the bdaddr of the remote device will never change */ + return ETH_ALEN; +} + +static guint32 +get_connection_bt_type (NMConnection *connection) +{ + NMSettingBluetooth *s_bt; + const char *bt_type; + + s_bt = nm_connection_get_setting_bluetooth (connection); + if (!s_bt) + return NM_BT_CAPABILITY_NONE; + + bt_type = nm_setting_bluetooth_get_connection_type (s_bt); + g_assert (bt_type); + + if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)) + return NM_BT_CAPABILITY_DUN; + else if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) + return NM_BT_CAPABILITY_NAP; + + return NM_BT_CAPABILITY_NONE; +} + +static gboolean +can_auto_connect (NMDevice *device, + NMConnection *connection, + char **specific_object) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + guint32 bt_type; + + if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->can_auto_connect (device, connection, specific_object)) + return FALSE; + + /* Can't auto-activate a DUN connection without ModemManager */ + bt_type = get_connection_bt_type (connection); + if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) + return FALSE; + + return TRUE; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + NMSettingConnection *s_con; + NMSettingBluetooth *s_bt; + const GByteArray *array; + guint32 bt_type; + + if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_BLUETOOTH_SETTING_NAME)) + return FALSE; + + s_bt = nm_connection_get_setting_bluetooth (connection); + if (!s_bt) + return FALSE; + + bt_type = get_connection_bt_type (connection); + if (!(bt_type & priv->capabilities)) + return FALSE; + + array = nm_setting_bluetooth_get_bdaddr (s_bt); + if (!array || (array->len != ETH_ALEN)) + return FALSE; + + if (memcmp (priv->bdaddr, array->data, ETH_ALEN) != 0) + return FALSE; + + return TRUE; +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + guint32 bt_type; + + bt_type = get_connection_bt_type (connection); + if (!(bt_type & priv->capabilities)) + return FALSE; + + /* DUN connections aren't available without ModemManager */ + if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) + return FALSE; + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + NMSettingBluetooth *s_bt; + const GByteArray *setting_bdaddr; + const char *ctype; + gboolean is_dun = FALSE, is_pan = FALSE; + NMSettingGsm *s_gsm; + NMSettingCdma *s_cdma; + NMSettingSerial *s_serial; + NMSettingPPP *s_ppp; + const char *format = NULL, *preferred = NULL; + + s_gsm = nm_connection_get_setting_gsm (connection); + s_cdma = nm_connection_get_setting_cdma (connection); + s_serial = nm_connection_get_setting_serial (connection); + s_ppp = nm_connection_get_setting_ppp (connection); + + s_bt = nm_connection_get_setting_bluetooth (connection); + if (!s_bt) { + s_bt = (NMSettingBluetooth *) nm_setting_bluetooth_new (); + nm_connection_add_setting (connection, NM_SETTING (s_bt)); + } + + ctype = nm_setting_bluetooth_get_connection_type (s_bt); + if (ctype) { + if (!strcmp (ctype, NM_SETTING_BLUETOOTH_TYPE_DUN)) + is_dun = TRUE; + else if (!strcmp (ctype, NM_SETTING_BLUETOOTH_TYPE_PANU)) + is_pan = TRUE; + } else { + if (s_gsm || s_cdma) + is_dun = TRUE; + else if (priv->capabilities & NM_BT_CAPABILITY_NAP) + is_pan = TRUE; + } + + if (is_pan) { + /* Make sure the device supports PAN */ + if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) { + g_set_error_literal (error, + NM_SETTING_BLUETOOTH_ERROR, + NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY, + "PAN required but Bluetooth device does not support NAP"); + return FALSE; + } + + /* PAN can't use any DUN-related settings */ + if (s_gsm || s_cdma || s_serial || s_ppp) { + g_set_error_literal (error, + NM_SETTING_BLUETOOTH_ERROR, + NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY, + "PAN incompatible with GSM, CDMA, or serial settings"); + return FALSE; + } + + g_object_set (G_OBJECT (s_bt), + NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL); + + format = _("PAN connection %d"); + } else if (is_dun) { + /* Make sure the device supports PAN */ + if (!(priv->capabilities & NM_BT_CAPABILITY_DUN)) { + g_set_error_literal (error, + NM_SETTING_BLUETOOTH_ERROR, + NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY, + "DUN required but Bluetooth device does not support DUN"); + return FALSE; + } + + /* Need at least a GSM or a CDMA setting */ + if (!s_gsm && !s_cdma) { + g_set_error_literal (error, + NM_SETTING_BLUETOOTH_ERROR, + NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY, + "Setting requires DUN but no GSM or CDMA setting is present"); + return FALSE; + } + + g_object_set (G_OBJECT (s_bt), + NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_DUN, + NULL); + + if (s_gsm) { + format = _("GSM connection %d"); + if (!nm_setting_gsm_get_number (s_gsm)) + g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL); + } else if (s_cdma) { + format = _("CDMA connection %d"); + if (!nm_setting_cdma_get_number (s_cdma)) + g_object_set (G_OBJECT (s_cdma), NM_SETTING_GSM_NUMBER, "#777", NULL); + } else + format = _("DUN connection %d"); + } else { + g_set_error_literal (error, + NM_SETTING_BLUETOOTH_ERROR, + NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY, + "Unknown/unhandled Bluetooth connection type"); + return FALSE; + } + + nm_utils_complete_generic (connection, + NM_SETTING_BLUETOOTH_SETTING_NAME, + existing_connections, + format, + preferred, + is_dun ? FALSE : TRUE); /* No IPv6 yet for DUN */ + + setting_bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt); + if (setting_bdaddr) { + /* Make sure the setting BT Address (if any) matches the device's */ + if (memcmp (setting_bdaddr->data, priv->bdaddr, ETH_ALEN)) { + g_set_error_literal (error, + NM_SETTING_BLUETOOTH_ERROR, + NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY, + NM_SETTING_BLUETOOTH_BDADDR); + return FALSE; + } + } else { + GByteArray *bdaddr; + const guint8 null_mac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + + /* Lock the connection to this device by default */ + if (memcmp (priv->bdaddr, null_mac, ETH_ALEN)) { + bdaddr = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (bdaddr, priv->bdaddr, ETH_ALEN); + g_object_set (G_OBJECT (s_bt), NM_SETTING_BLUETOOTH_BDADDR, bdaddr, NULL); + g_byte_array_free (bdaddr, TRUE); + } + } + + return TRUE; +} + +/*****************************************************************************/ +/* IP method PPP */ + +static void +ppp_stats (NMModem *modem, + guint32 in_bytes, + guint32 out_bytes, + gpointer user_data) +{ + g_signal_emit (NM_DEVICE_BT (user_data), signals[PPP_STATS], 0, in_bytes, out_bytes); +} + +static void +ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + switch (nm_device_get_state (device)) { + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + break; + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + case NM_DEVICE_STATE_ACTIVATED: + if (nm_device_activate_ip4_state_in_conf (device)) + nm_device_activate_schedule_ip4_config_timeout (device); + else { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + break; + default: + break; + } +} + +static void +modem_auth_requested (NMModem *modem, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + /* Auth requests (PIN, PAP/CHAP passwords, etc) only get handled + * during activation. + */ + if (!nm_device_is_activating (device)) + return; + + nm_device_state_changed (device, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_NONE); +} + +static void +modem_auth_result (NMModem *modem, GError *error, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + if (error) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } else { + /* Otherwise, on success for GSM/CDMA secrets we need to schedule modem stage1 again */ + g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH); + if (!modem_stage1 (NM_DEVICE_BT (device), priv->modem, &reason)) + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + } +} + +static void +modem_prepare_result (NMModem *modem, + gboolean success, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDeviceState state; + + state = nm_device_get_state (device); + g_return_if_fail (state == NM_DEVICE_STATE_CONFIG || state == NM_DEVICE_STATE_NEED_AUTH); + + if (success) { + NMActRequest *req; + NMActStageReturn ret; + NMDeviceStateReason stage2_reason = NM_DEVICE_STATE_REASON_NONE; + + req = nm_device_get_act_request (device); + g_assert (req); + + ret = nm_modem_act_stage2_config (modem, req, &stage2_reason); + switch (ret) { + case NM_ACT_STAGE_RETURN_POSTPONE: + break; + case NM_ACT_STAGE_RETURN_SUCCESS: + nm_device_activate_schedule_stage3_ip_config_start (device); + break; + case NM_ACT_STAGE_RETURN_FAILURE: + default: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, stage2_reason); + break; + } + } else { + if (reason == NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT) { + /* If the connect failed because the SIM PIN was wrong don't allow + * the device to be auto-activated anymore, which would risk locking + * the SIM if the incorrect PIN continues to be used. + */ + g_object_set (G_OBJECT (device), NM_DEVICE_AUTOCONNECT, FALSE, NULL); + nm_log_info (LOGD_MB, "(%s): disabling autoconnect due to failed SIM PIN", + nm_device_get_iface (device)); + } + + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + } +} + +static void +device_state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + + if (priv->modem) + nm_modem_device_state_changed (priv->modem, new_state, old_state, reason); +} + +static void +modem_ip4_config_result (NMModem *self, + NMIP4Config *config, + GError *error, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + g_return_if_fail (nm_device_activate_ip4_state_in_conf (device) == TRUE); + + if (error) { + nm_log_warn (LOGD_MB | LOGD_IP4 | LOGD_BT, + "(%s): retrieving IP4 configuration failed: (%d) %s", + nm_device_get_ip_iface (device), + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else + nm_device_activate_schedule_ip4_config_result (device, config); +} + +static void +data_port_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + + nm_device_set_ip_iface (self, nm_modem_get_data_port (modem)); +} + +static gboolean +modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *reason) +{ + NMActRequest *req; + NMActStageReturn ret; + + g_return_val_if_fail (reason != NULL, FALSE); + + req = nm_device_get_act_request (NM_DEVICE (self)); + g_assert (req); + + ret = nm_modem_act_stage1_prepare (modem, req, reason); + switch (ret) { + case NM_ACT_STAGE_RETURN_POSTPONE: + case NM_ACT_STAGE_RETURN_SUCCESS: + /* Success, wait for the 'prepare-result' signal */ + return TRUE; + case NM_ACT_STAGE_RETURN_FAILURE: + default: + break; + } + + return FALSE; +} + +/*****************************************************************************/ + +static void +modem_cleanup (NMDeviceBt *self) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + + if (priv->modem) { + g_signal_handlers_disconnect_matched (priv->modem, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self); + g_clear_object (&priv->modem); + } +} + +static void +modem_state_cb (NMModem *modem, + NMModemState new_state, + NMModemState old_state, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDeviceState dev_state = nm_device_get_state (device); + + if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING) { + /* Will be called whenever something external to NM disables the + * modem directly through ModemManager. + */ + if (nm_device_is_activating (device) || dev_state == NM_DEVICE_STATE_ACTIVATED) { + nm_device_state_changed (device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + return; + } + } + + if (new_state < NM_MODEM_STATE_CONNECTING && + old_state >= NM_MODEM_STATE_CONNECTING && + dev_state >= NM_DEVICE_STATE_NEED_AUTH && + dev_state <= NM_DEVICE_STATE_ACTIVATED) { + /* Fail the device if the modem disconnects unexpectedly while the + * device is activating/activated. */ + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return; + } +} + +static void +modem_removed_cb (NMModem *modem, gpointer user_data) +{ + NMDeviceBt *self = NM_DEVICE_BT (user_data); + NMDeviceState state; + + /* Fail the device if the modem was removed while active */ + state = nm_device_get_state (NM_DEVICE (self)); + if ( state == NM_DEVICE_STATE_ACTIVATED + || nm_device_is_activating (NM_DEVICE (self))) { + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + } else + modem_cleanup (self); +} + +static gboolean +component_added (NMDevice *device, GObject *component) +{ + NMDeviceBt *self = NM_DEVICE_BT (device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + NMModem *modem; + const gchar *modem_data_port; + const gchar *modem_control_port; + char *base; + NMDeviceState state; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + if (!NM_IS_MODEM (component)) + return FALSE; + modem = NM_MODEM (component); + + modem_data_port = nm_modem_get_data_port (modem); + modem_control_port = nm_modem_get_control_port (modem); + g_return_val_if_fail (modem_data_port != NULL || modem_control_port != NULL, FALSE); + + if (!priv->rfcomm_iface) + return FALSE; + + base = g_path_get_basename (priv->rfcomm_iface); + if (g_strcmp0 (base, modem_data_port) && g_strcmp0 (base, modem_control_port)) { + g_free (base); + return FALSE; + } + g_free (base); + + /* Got the modem */ + if (priv->timeout_id) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + /* Can only accept the modem in stage2, but since the interface matched + * what we were expecting, don't let anything else claim the modem either. + */ + state = nm_device_get_state (NM_DEVICE (self)); + if (state != NM_DEVICE_STATE_CONFIG) { + nm_log_warn (LOGD_BT | LOGD_MB, + "(%s): modem found but device not in correct state (%d)", + nm_device_get_iface (NM_DEVICE (self)), + nm_device_get_state (NM_DEVICE (self))); + return TRUE; + } + + nm_log_info (LOGD_BT | LOGD_MB, + "Activation (%s/bluetooth) Stage 2 of 5 (Device Configure) modem found.", + nm_device_get_iface (NM_DEVICE (self))); + + if (priv->modem) { + g_warn_if_reached (); + modem_cleanup (self); + } + + priv->modem = g_object_ref (modem); + g_signal_connect (modem, NM_MODEM_PPP_STATS, G_CALLBACK (ppp_stats), self); + g_signal_connect (modem, NM_MODEM_PPP_FAILED, G_CALLBACK (ppp_failed), self); + g_signal_connect (modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK (modem_prepare_result), self); + g_signal_connect (modem, NM_MODEM_IP4_CONFIG_RESULT, G_CALLBACK (modem_ip4_config_result), self); + g_signal_connect (modem, NM_MODEM_AUTH_REQUESTED, G_CALLBACK (modem_auth_requested), self); + g_signal_connect (modem, NM_MODEM_AUTH_RESULT, G_CALLBACK (modem_auth_result), self); + g_signal_connect (modem, NM_MODEM_STATE_CHANGED, G_CALLBACK (modem_state_cb), self); + g_signal_connect (modem, NM_MODEM_REMOVED, G_CALLBACK (modem_removed_cb), self); + + /* In the old ModemManager the data port is known from the very beginning; + * while in the new ModemManager the data port is set afterwards when the bearer gets + * created */ + if (modem_data_port) + nm_device_set_ip_iface (NM_DEVICE (self), modem_data_port); + g_signal_connect (modem, "notify::" NM_MODEM_DATA_PORT, G_CALLBACK (data_port_changed_cb), self); + + /* Kick off the modem connection */ + if (!modem_stage1 (self, modem, &reason)) + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, reason); + + return TRUE; +} + +static gboolean +modem_find_timeout (gpointer user_data) +{ + NMDeviceBt *self = NM_DEVICE_BT (user_data); + + NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0; + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND); + return FALSE; +} + +static void +check_connect_continue (NMDeviceBt *self) +{ + NMDevice *device = NM_DEVICE (self); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + gboolean pan = (priv->bt_type == NM_BT_CAPABILITY_NAP); + gboolean dun = (priv->bt_type == NM_BT_CAPABILITY_DUN); + + if (!priv->connected || !priv->have_iface) + return; + + nm_log_info (LOGD_BT, "Activation (%s %s/bluetooth) Stage 2 of 5 (Device Configure) " + "successful. Will connect via %s.", + nm_device_get_iface (device), + nm_device_get_ip_iface (device), + dun ? "DUN" : (pan ? "PAN" : "unknown")); + + /* Kill the connect timeout since we're connected now */ + if (priv->timeout_id) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + if (pan) { + /* Bluez says we're connected now. Start IP config. */ + nm_device_activate_schedule_stage3_ip_config_start (device); + } else if (dun) { + /* Wait for ModemManager to find the modem */ + priv->timeout_id = g_timeout_add_seconds (30, modem_find_timeout, self); + + nm_log_info (LOGD_BT | LOGD_MB, "Activation (%s/bluetooth) Stage 2 of 5 (Device Configure) " + "waiting for modem to appear.", + nm_device_get_iface (device)); + } else + g_assert_not_reached (); +} + +static void +bluez_connect_cb (GObject *object, + GAsyncResult *res, + void *user_data) +{ + NMDeviceBt *self = NM_DEVICE_BT (user_data); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + GError *error = NULL; + const char *device; + + device = nm_bluez_device_connect_finish (NM_BLUEZ_DEVICE (object), + res, &error); + + if (!device) { + nm_log_warn (LOGD_BT, "Error connecting with bluez: %s", + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; + } + + if (priv->bt_type == NM_BT_CAPABILITY_DUN) { + g_free (priv->rfcomm_iface); + priv->rfcomm_iface = g_strdup (device); + } else if (priv->bt_type == NM_BT_CAPABILITY_NAP) { + nm_device_set_ip_iface (NM_DEVICE (self), device); + } + + nm_log_dbg (LOGD_BT, "(%s): connect request successful", + nm_device_get_iface (NM_DEVICE (self))); + + /* Stage 3 gets scheduled when Bluez says we're connected */ + priv->have_iface = TRUE; + check_connect_continue (self); +} + +static void +bluez_connected_changed (NMBluezDevice *bt_device, + GParamSpec *pspec, + NMDevice *device) +{ + NMDeviceBt *self = NM_DEVICE_BT (device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + gboolean connected; + NMDeviceState state; + + state = nm_device_get_state (device); + connected = nm_bluez_device_get_connected (bt_device); + if (connected) { + if (state == NM_DEVICE_STATE_CONFIG) { + nm_log_dbg (LOGD_BT, "(%s): connected to the device", + nm_device_get_iface (device)); + + priv->connected = TRUE; + check_connect_continue (self); + } + } else { + gboolean fail = FALSE; + + /* Bluez says we're disconnected from the device. Suck. */ + + if (nm_device_is_activating (device)) { + nm_log_info (LOGD_BT, + "Activation (%s/bluetooth): bluetooth link disconnected.", + nm_device_get_iface (device)); + fail = TRUE; + } else if (state == NM_DEVICE_STATE_ACTIVATED) { + nm_log_info (LOGD_BT, "(%s): bluetooth link disconnected.", + nm_device_get_iface (device)); + fail = TRUE; + } + + if (fail) { + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CARRIER); + priv->connected = FALSE; + } + } +} + +static gboolean +bt_connect_timeout (gpointer user_data) +{ + NMDeviceBt *self = NM_DEVICE_BT (user_data); + + nm_log_dbg (LOGD_BT, "(%s): initial connection timed out", + nm_device_get_iface (NM_DEVICE (self))); + + NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0; + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return FALSE; +} + +static NMActStageReturn +act_stage2_config (NMDevice *device, NMDeviceStateReason *reason) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + NMConnection *connection; + + connection = nm_device_get_connection (device); + g_assert (connection); + priv->bt_type = get_connection_bt_type (connection); + if (priv->bt_type == NM_BT_CAPABILITY_NONE) { + // FIXME: set a reason code + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (priv->bt_type == NM_BT_CAPABILITY_DUN && !priv->mm_running) { + *reason = NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + nm_log_dbg (LOGD_BT, "(%s): requesting connection to the device", + nm_device_get_iface (device)); + + /* Connect to the BT device */ + nm_bluez_device_connect_async (priv->bt_device, + priv->bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP), + bluez_connect_cb, device); + + if (priv->timeout_id) + g_source_remove (priv->timeout_id); + priv->timeout_id = g_timeout_add_seconds (30, bt_connect_timeout, device); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static NMActStageReturn +act_stage3_ip4_config_start (NMDevice *device, + NMIP4Config **out_config, + NMDeviceStateReason *reason) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + NMActStageReturn ret; + + if (priv->bt_type == NM_BT_CAPABILITY_DUN) { + ret = nm_modem_stage3_ip4_config_start (NM_DEVICE_BT_GET_PRIVATE (device)->modem, + device, + NM_DEVICE_CLASS (nm_device_bt_parent_class), + reason); + } else + ret = NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip4_config_start (device, out_config, reason); + + return ret; +} + +static NMActStageReturn +act_stage3_ip6_config_start (NMDevice *device, + NMIP6Config **out_config, + NMDeviceStateReason *reason) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + NMActStageReturn ret; + + if (priv->bt_type == NM_BT_CAPABILITY_DUN) { + ret = nm_modem_stage3_ip6_config_start (NM_DEVICE_BT_GET_PRIVATE (device)->modem, + device, + NM_DEVICE_CLASS (nm_device_bt_parent_class), + reason); + } else + ret = NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip6_config_start (device, out_config, reason); + + return ret; +} + +static void +deactivate (NMDevice *device) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + + priv->have_iface = FALSE; + priv->connected = FALSE; + + if (priv->bt_type == NM_BT_CAPABILITY_DUN) { + if (priv->modem) { + nm_modem_deactivate (priv->modem, device); + + /* Since we're killing the Modem object before it'll get the + * state change signal, simulate the state change here. + */ + nm_modem_device_state_changed (priv->modem, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_ACTIVATED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + modem_cleanup (NM_DEVICE_BT (device)); + } + } + + if (priv->bt_type != NM_BT_CAPABILITY_NONE) + nm_bluez_device_disconnect (priv->bt_device); + + if (priv->timeout_id) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + priv->bt_type = NM_BT_CAPABILITY_NONE; + + g_free (priv->rfcomm_iface); + priv->rfcomm_iface = NULL; + + if (NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate) + NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate (device); +} + +static void +bluez_device_removed (NMBluezDevice *bdev, gpointer user_data) +{ + g_signal_emit_by_name (NM_DEVICE_BT (user_data), NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +static gboolean +is_available (NMDevice *dev) +{ + NMDeviceBt *self = NM_DEVICE_BT (dev); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + + /* PAN doesn't need ModemManager, so devices that support it are always available */ + if (priv->capabilities & NM_BT_CAPABILITY_NAP) + return TRUE; + + /* DUN requires ModemManager */ + return priv->mm_running; +} + +static void +handle_availability_change (NMDeviceBt *self, + gboolean old_available, + NMDeviceStateReason unavailable_reason) +{ + NMDevice *device = NM_DEVICE (self); + NMDeviceState state; + gboolean available; + + state = nm_device_get_state (device); + if (state < NM_DEVICE_STATE_UNAVAILABLE) { + nm_log_dbg (LOGD_BT, "(%s): availability blocked by UNMANAGED state", + nm_device_get_iface (device)); + return; + } + + available = nm_device_is_available (device); + if (available == old_available) + return; + + if (available) { + if (state != NM_DEVICE_STATE_UNAVAILABLE) + nm_log_warn (LOGD_CORE | LOGD_BT, "not in expected unavailable state!"); + + nm_device_state_changed (device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_NONE); + } else { + nm_device_state_changed (device, + NM_DEVICE_STATE_UNAVAILABLE, + unavailable_reason); + } +} + +static void +set_mm_running (NMDeviceBt *self, gboolean running) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + gboolean old_available; + + if (priv->mm_running == running) + return; + + nm_log_dbg (LOGD_BT, "(%s): ModemManager now %s", + nm_device_get_iface (NM_DEVICE (self)), + running ? "available" : "unavailable"); + + old_available = nm_device_is_available (NM_DEVICE (self)); + priv->mm_running = running; + handle_availability_change (self, old_available, NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE); + + /* Need to recheck available connections whenever MM appears or disappears, + * since the device could be both DUN and NAP capable and thus may not + * change state (which rechecks available connections) when MM comes and goes. + */ + if (priv->capabilities & NM_BT_CAPABILITY_DUN) + nm_device_recheck_available_connections (NM_DEVICE (self)); +} + +static void +mm_name_owner_changed (NMDBusManager *dbus_mgr, + const char *name, + const char *old_owner, + const char *new_owner, + NMDeviceBt *self) +{ + gboolean old_owner_good; + gboolean new_owner_good; + + /* Can't handle the signal if its not from the modem service */ + if ( strcmp (MM_OLD_DBUS_SERVICE, name) != 0 +#if WITH_MODEM_MANAGER_1 + && strcmp (MM_NEW_DBUS_SERVICE, name) != 0 +#endif + ) + return; + + old_owner_good = (old_owner && strlen (old_owner)); + new_owner_good = (new_owner && strlen (new_owner)); + + if (!old_owner_good && new_owner_good) + set_mm_running (self, TRUE); + else if (old_owner_good && !new_owner_good) + set_mm_running (self, FALSE); +} + +/*****************************************************************************/ + +NMDevice * +nm_device_bt_new (NMBluezDevice *bt_device, + const char *udi, + const char *bdaddr, + const char *name, + guint32 capabilities) +{ + g_return_val_if_fail (udi != NULL, NULL); + g_return_val_if_fail (bdaddr != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (capabilities != NM_BT_CAPABILITY_NONE, NULL); + g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (bt_device), NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BT, + NM_DEVICE_UDI, udi, + NM_DEVICE_IFACE, bdaddr, + NM_DEVICE_DRIVER, "bluez", + NM_DEVICE_HW_ADDRESS, bdaddr, + NM_DEVICE_BT_DEVICE, bt_device, + NM_DEVICE_BT_NAME, name, + NM_DEVICE_BT_CAPABILITIES, capabilities, + NM_DEVICE_TYPE_DESC, "Bluetooth", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BT, + NULL); +} + +static void +nm_device_bt_init (NMDeviceBt *self) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + gboolean mm_running; + + priv->dbus_mgr = nm_dbus_manager_get (); + + priv->mm_watch_id = g_signal_connect (priv->dbus_mgr, + NM_DBUS_MANAGER_NAME_OWNER_CHANGED, + G_CALLBACK (mm_name_owner_changed), + self); + + /* Initial check to see if ModemManager is running */ + mm_running = nm_dbus_manager_name_has_owner (priv->dbus_mgr, MM_OLD_DBUS_SERVICE); +#if WITH_MODEM_MANAGER_1 + if (!mm_running) + mm_running = nm_dbus_manager_name_has_owner (priv->dbus_mgr, MM_NEW_DBUS_SERVICE); +#endif + set_mm_running (self, mm_running); +} + +static void +constructed (GObject *object) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object); + const guint8 *my_hwaddr; + guint my_hwaddr_len = 0; + + G_OBJECT_CLASS (nm_device_bt_parent_class)->constructed (object); + + my_hwaddr = nm_device_get_hw_address (NM_DEVICE (object), &my_hwaddr_len); + g_assert (my_hwaddr); + g_assert_cmpint (my_hwaddr_len, ==, ETH_ALEN); + memcpy (priv->bdaddr, my_hwaddr, ETH_ALEN); + + /* Watch for BT device property changes */ + g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED, + G_CALLBACK (bluez_connected_changed), + object); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_BT_NAME: + /* Construct only */ + priv->name = g_value_dup_string (value); + break; + case PROP_BT_CAPABILITIES: + /* Construct only */ + priv->capabilities = g_value_get_uint (value); + break; + case PROP_BT_DEVICE: + /* Construct only */ + priv->bt_device = g_value_dup_object (value); + g_signal_connect (priv->bt_device, "removed", G_CALLBACK (bluez_device_removed), object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_BT_NAME: + g_value_set_string (value, priv->name); + break; + case PROP_BT_CAPABILITIES: + g_value_set_uint (value, priv->capabilities); + break; + case PROP_BT_DEVICE: + g_value_set_object (value, priv->bt_device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object); + + if (priv->timeout_id) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object); + + if (priv->dbus_mgr && priv->mm_watch_id) { + g_signal_handler_disconnect (priv->dbus_mgr, priv->mm_watch_id); + priv->mm_watch_id = 0; + } + priv->dbus_mgr = NULL; + + modem_cleanup (NM_DEVICE_BT (object)); + g_clear_object (&priv->bt_device); + + G_OBJECT_CLASS (nm_device_bt_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object); + + g_free (priv->rfcomm_iface); + g_free (priv->name); + + G_OBJECT_CLASS (nm_device_bt_parent_class)->finalize (object); +} + +static void +nm_device_bt_class_init (NMDeviceBtClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceBtPrivate)); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + device_class->get_hw_address_length = get_hw_address_length; + device_class->can_auto_connect = can_auto_connect; + device_class->deactivate = deactivate; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start; + device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->is_available = is_available; + device_class->component_added = component_added; + + device_class->state_changed = device_state_changed; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_BT_NAME, + g_param_spec_string (NM_DEVICE_BT_NAME, + "Bluetooth device name", + "Bluetooth device name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_BT_CAPABILITIES, + g_param_spec_uint (NM_DEVICE_BT_CAPABILITIES, + "Bluetooth device capabilities", + "Bluetooth device capabilities", + NM_BT_CAPABILITY_NONE, G_MAXUINT, NM_BT_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_BT_DEVICE, + g_param_spec_object (NM_DEVICE_BT_DEVICE, + "NMBluezDevice object for the Device", + "NMBluezDevice object for the Device", + NM_TYPE_BLUEZ_DEVICE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /* Signals */ + signals[PPP_STATS] = + g_signal_new ("ppp-stats", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceBtClass, ppp_stats), + NULL, NULL, NULL, + G_TYPE_NONE, 2, + G_TYPE_UINT, G_TYPE_UINT); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_bt_object_info); + + dbus_g_error_domain_register (NM_BT_ERROR, NULL, NM_TYPE_BT_ERROR); +} diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h new file mode 100644 index 000000000..83732bc09 --- /dev/null +++ b/src/devices/bluetooth/nm-device-bt.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_BT_H +#define NM_DEVICE_BT_H + +#include <nm-device.h> +#include "nm-bluez-device.h" +#include "nm-modem.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_BT (nm_device_bt_get_type ()) +#define NM_DEVICE_BT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BT, NMDeviceBt)) +#define NM_DEVICE_BT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_BT, NMDeviceBtClass)) +#define NM_IS_DEVICE_BT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_BT)) +#define NM_IS_DEVICE_BT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_BT)) +#define NM_DEVICE_BT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_BT, NMDeviceBtClass)) + +typedef enum { + NM_BT_ERROR_CONNECTION_NOT_BT = 0, /*< nick=ConnectionNotBt >*/ + NM_BT_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_BT_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMBtError; + +#define NM_DEVICE_BT_NAME "name" +#define NM_DEVICE_BT_CAPABILITIES "bt-capabilities" +#define NM_DEVICE_BT_DEVICE "bt-device" + +typedef struct { + NMDevice parent; +} NMDeviceBt; + +typedef struct { + NMDeviceClass parent; + + /* Signals */ + void (*ppp_stats) (NMDeviceBt *device, guint32 in_bytes, guint32 out_bytes); +} NMDeviceBtClass; + +GType nm_device_bt_get_type (void); + +NMDevice *nm_device_bt_new (NMBluezDevice *bt_device, + const char *udi, + const char *bdaddr, + const char *name, + guint32 capabilities); + +guint32 nm_device_bt_get_capabilities (NMDeviceBt *device); + +gboolean nm_device_bt_modem_added (NMDeviceBt *device, + NMModem *modem, + const char *driver); + +G_END_DECLS + +#endif /* NM_DEVICE_BT_H */ diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c new file mode 100644 index 000000000..6f8caeb40 --- /dev/null +++ b/src/devices/nm-device-bond.c @@ -0,0 +1,612 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2011 - 2012 Red Hat, Inc. + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> + +#include <netinet/ether.h> +#include <errno.h> +#include <stdlib.h> + +#include "gsystem-local-alloc.h" +#include "nm-device-bond.h" +#include "nm-logging.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-platform.h" +#include "nm-dbus-glib-types.h" +#include "nm-dbus-manager.h" +#include "nm-enum-types.h" + +#include "nm-device-bond-glue.h" + + +G_DEFINE_TYPE (NMDeviceBond, nm_device_bond, NM_TYPE_DEVICE) + +#define NM_DEVICE_BOND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_BOND, NMDeviceBondPrivate)) + +#define NM_BOND_ERROR (nm_bond_error_quark ()) + +typedef struct { + int dummy; +} NMDeviceBondPrivate; + +enum { + PROP_0, + PROP_SLAVES, + + LAST_PROP +}; + +/******************************************************************/ + +static GQuark +nm_bond_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-bond-error"); + return quark; +} + +/******************************************************************/ + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + return NM_DEVICE_CAP_CARRIER_DETECT; +} + +static gboolean +is_available (NMDevice *dev) +{ + if (NM_DEVICE_GET_CLASS (dev)->is_up) + return NM_DEVICE_GET_CLASS (dev)->is_up (dev); + return FALSE; +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + /* Connections are always available because the carrier state is determined + * by the slave carrier states, not the bonds's state. + */ + return TRUE; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + const char *iface; + NMSettingBond *s_bond; + + if (!NM_DEVICE_CLASS (nm_device_bond_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_bond = nm_connection_get_setting_bond (connection); + if (!s_bond || !nm_connection_is_type (connection, NM_SETTING_BOND_SETTING_NAME)) + return FALSE; + + /* Bond connections must specify the virtual interface name */ + iface = nm_connection_get_virtual_iface_name (connection); + if (!iface || strcmp (nm_device_get_iface (device), iface)) + return FALSE; + + /* FIXME: match bond properties like mode, etc? */ + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMSettingBond *s_bond, *tmp; + guint32 i = 0; + char *name; + const GSList *iter; + gboolean found; + + nm_utils_complete_generic (connection, + NM_SETTING_BOND_SETTING_NAME, + existing_connections, + _("Bond connection %d"), + NULL, + TRUE); + + s_bond = nm_connection_get_setting_bond (connection); + if (!s_bond) { + s_bond = (NMSettingBond *) nm_setting_bond_new (); + nm_connection_add_setting (connection, NM_SETTING (s_bond)); + } + + /* Grab the first name that doesn't exist in either our connections + * or a device on the system. + */ + while (i < 500 && !nm_setting_bond_get_interface_name (s_bond)) { + name = g_strdup_printf ("bond%u", i); + /* check interface names */ + if (!nm_platform_link_exists (name)) { + /* check existing bond connections */ + for (iter = existing_connections, found = FALSE; iter; iter = g_slist_next (iter)) { + NMConnection *candidate = iter->data; + + tmp = nm_connection_get_setting_bond (candidate); + if (tmp && nm_connection_is_type (candidate, NM_SETTING_BOND_SETTING_NAME)) { + if (g_strcmp0 (nm_setting_bond_get_interface_name (tmp), name) == 0) { + found = TRUE; + break; + } + } + } + + if (!found) + g_object_set (G_OBJECT (s_bond), NM_SETTING_BOND_INTERFACE_NAME, name, NULL); + } + + g_free (name); + i++; + } + + return TRUE; +} + +/******************************************************************/ + +static gboolean +set_bond_attr (NMDevice *device, const char *attr, const char *value) +{ + gboolean ret; + int ifindex = nm_device_get_ifindex (device); + + ret = nm_platform_master_set_option (ifindex, attr, value); + if (!ret) { + nm_log_warn (LOGD_HW, "(%s): failed to set bonding attribute " + "'%s' to '%s'", nm_device_get_ip_iface (device), attr, value); + } + return ret; +} + +/* Ignore certain bond options if they are zero (off/disabled) */ +static gboolean +ignore_if_zero (const char *option, const char *value) +{ + if (strcmp (option, "arp_interval") && + strcmp (option, "miimon") && + strcmp (option, "downdelay") && + strcmp (option, "updelay")) + return FALSE; + + return g_strcmp0 (value, "0") == 0 ? TRUE : FALSE; +} + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMSettingBond *s_bond = nm_connection_get_setting_bond (connection); + const char *ifname = nm_device_get_iface (device); + int ifindex = nm_device_get_ifindex (device); + const char **options; + + if (!s_bond) { + s_bond = (NMSettingBond *) nm_setting_bond_new (); + nm_connection_add_setting (connection, (NMSetting *) s_bond); + g_object_set (s_bond, NM_SETTING_BOND_INTERFACE_NAME, ifname, NULL); + } + + /* Read bond options from sysfs and update the Bond setting to match */ + options = nm_setting_bond_get_valid_options (s_bond); + while (options && *options) { + gs_free char *value = nm_platform_master_get_option (ifindex, *options); + const char *defvalue = nm_setting_bond_get_option_default (s_bond, *options); + + if (value && !ignore_if_zero (*options, value) && (g_strcmp0 (value, defvalue) != 0)) { + /* Replace " " with "," for arp_ip_targets from the kernel */ + if (strcmp (*options, "arp_ip_target") == 0) { + char *p = value; + + while (p && *p) { + if (*p == ' ') + *p = ','; + p++; + } + } + + nm_setting_bond_add_option (s_bond, *options, value); + } + options++; + } +} + +static void +set_arp_targets (NMDevice *device, + const char *value, + const char *delim, + const char *prefix) +{ + char **items, **iter, *tmp; + + if (!value || !*value) + return; + + items = g_strsplit_set (value, delim, 0); + for (iter = items; iter && *iter; iter++) { + if (*iter[0]) { + tmp = g_strdup_printf ("%s%s", prefix, *iter); + set_bond_attr (device, "arp_ip_target", tmp); + g_free (tmp); + } + } + g_strfreev (items); +} + +static void +set_simple_option (NMDevice *device, + const char *attr, + NMSettingBond *s_bond, + const char *opt) +{ + const char *value; + + value = nm_setting_bond_get_option_by_name (s_bond, opt); + if (!value) + value = nm_setting_bond_get_option_default (s_bond, opt); + set_bond_attr (device, attr, value); +} + +static NMActStageReturn +apply_bonding_config (NMDevice *device) +{ + NMConnection *connection; + NMSettingBond *s_bond; + int ifindex = nm_device_get_ifindex (device); + const char *mode, *value; + char *contents; + gboolean set_arp_interval = TRUE; + + /* Option restrictions: + * + * arp_interval conflicts miimon > 0 + * arp_interval conflicts [ alb, tlb ] + * arp_validate needs [ active-backup ] + * downdelay needs miimon + * updelay needs miimon + * primary needs [ active-backup, tlb, alb ] + * + * clearing miimon requires that arp_interval be 0, but clearing + * arp_interval doesn't require miimon to be 0 + */ + + connection = nm_device_get_connection (device); + g_assert (connection); + s_bond = nm_connection_get_setting_bond (connection); + g_assert (s_bond); + + mode = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE); + if (mode == NULL) + mode = "balance-rr"; + + value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MIIMON); + if (value && atoi (value)) { + /* clear arp interval */ + set_bond_attr (device, "arp_interval", "0"); + set_arp_interval = FALSE; + + set_bond_attr (device, "miimon", value); + set_simple_option (device, "updelay", s_bond, NM_SETTING_BOND_OPTION_UPDELAY); + set_simple_option (device, "downdelay", s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY); + } else if (!value) { + /* If not given, and arp_interval is not given, default to 100 */ + long int val_int; + char *end; + + value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL); + errno = 0; + val_int = strtol (value ? value : "0", &end, 10); + if (!value || (val_int == 0 && errno == 0 && *end == '\0')) + set_bond_attr (device, "miimon", "100"); + } + + /* The stuff after 'mode' requires the given mode or doesn't care */ + set_bond_attr (device, "mode", mode); + + /* arp_interval not compatible with ALB, TLB */ + if (g_strcmp0 (mode, "balance-alb") == 0 || g_strcmp0 (mode, "balance-tlb") == 0) + set_arp_interval = FALSE; + + if (set_arp_interval) { + set_simple_option (device, "arp_interval", s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL); + + /* Just let miimon get cleared automatically; even setting miimon to + * 0 (disabled) clears arp_interval. + */ + } + + value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_VALIDATE); + /* arp_validate > 0 only valid in active-backup mode */ + if ( value + && g_strcmp0 (value, "0") != 0 + && g_strcmp0 (value, "none") != 0 + && g_strcmp0 (mode, "active-backup") == 0) + set_bond_attr (device, "arp_validate", value); + else + set_bond_attr (device, "arp_validate", "0"); + + if ( g_strcmp0 (mode, "active-backup") == 0 + || g_strcmp0 (mode, "balance-alb") == 0 + || g_strcmp0 (mode, "balance-tlb") == 0) { + value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_PRIMARY); + set_bond_attr (device, "primary", value ? value : ""); + } + + /* Clear ARP targets */ + contents = nm_platform_master_get_option (ifindex, "arp_ip_target"); + set_arp_targets (device, contents, " \n", "-"); + g_free (contents); + + /* Add new ARP targets */ + value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET); + set_arp_targets (device, value, ",", "+"); + + set_simple_option (device, "primary_reselect", s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT); + set_simple_option (device, "fail_over_mac", s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC); + set_simple_option (device, "use_carrier", s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER); + set_simple_option (device, "ad_select", s_bond, NM_SETTING_BOND_OPTION_AD_SELECT); + set_simple_option (device, "xmit_hash_policy", s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY); + set_simple_option (device, "resend_igmp", s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP); + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + + +static NMActStageReturn +act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; + gboolean no_firmware = FALSE; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + ret = NM_DEVICE_CLASS (nm_device_bond_parent_class)->act_stage1_prepare (dev, reason); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) + return ret; + + /* Interface must be down to set bond options */ + nm_device_take_down (dev, TRUE); + ret = apply_bonding_config (dev); + nm_device_bring_up (dev, TRUE, &no_firmware); + + return ret; +} + +static gboolean +enslave_slave (NMDevice *device, + NMDevice *slave, + NMConnection *connection, + gboolean configure) +{ + gboolean success = TRUE, no_firmware = FALSE; + const char *iface = nm_device_get_ip_iface (device); + const char *slave_iface = nm_device_get_ip_iface (slave); + + nm_device_master_check_slave_physical_port (device, slave, LOGD_BOND); + + if (configure) { + nm_device_take_down (slave, TRUE); + success = nm_platform_link_enslave (nm_device_get_ip_ifindex (device), + nm_device_get_ip_ifindex (slave)); + nm_device_bring_up (slave, TRUE, &no_firmware); + + if (!success) + return FALSE; + + nm_log_info (LOGD_BOND, "(%s): enslaved bond slave %s", iface, slave_iface); + } else + nm_log_info (LOGD_BOND, "(%s): bond slave %s was enslaved", iface, slave_iface); + + g_object_notify (G_OBJECT (device), NM_DEVICE_BOND_SLAVES); + return TRUE; +} + +static gboolean +release_slave (NMDevice *device, + NMDevice *slave, + gboolean configure) +{ + gboolean success = TRUE, no_firmware = FALSE; + + if (configure) { + success = nm_platform_link_release (nm_device_get_ip_ifindex (device), + nm_device_get_ip_ifindex (slave)); + + if (success) { + nm_log_info (LOGD_BOND, "(%s): released bond slave %s", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } else { + nm_log_warn (LOGD_BOND, "(%s): failed to release bond slave %s", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } + } else { + nm_log_info (LOGD_BOND, "(%s): bond slave %s was released", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } + + if (success) + g_object_notify (G_OBJECT (device), NM_DEVICE_BOND_SLAVES); + + if (configure) { + /* Kernel bonding code "closes" the slave when releasing it, (which clears + * IFF_UP), so we must bring it back up here to ensure carrier changes and + * other state is noticed by the now-released slave. + */ + if (!nm_device_bring_up (slave, TRUE, &no_firmware)) { + nm_log_warn (LOGD_BOND, "(%s): released bond slave could not be brought up.", + nm_device_get_iface (slave)); + } + } + + return success; +} + +/******************************************************************/ + +NMDevice * +nm_device_bond_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BOND, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_DRIVER, "bonding", + NM_DEVICE_TYPE_DESC, "Bond", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BOND, + NM_DEVICE_IS_MASTER, TRUE, + NULL); +} + +NMDevice * +nm_device_bond_new_for_connection (NMConnection *connection) +{ + const char *iface; + + g_return_val_if_fail (connection != NULL, NULL); + + iface = nm_connection_get_virtual_iface_name (connection); + g_return_val_if_fail (iface != NULL, NULL); + + if ( !nm_platform_bond_add (iface) + && nm_platform_get_error () != NM_PLATFORM_ERROR_EXISTS) { + nm_log_warn (LOGD_DEVICE | LOGD_BOND, "(%s): failed to create bonding master interface for '%s': %s", + iface, nm_connection_get_id (connection), + nm_platform_get_error_msg ()); + return NULL; + } + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BOND, + NM_DEVICE_IFACE, iface, + NM_DEVICE_DRIVER, "bonding", + NM_DEVICE_TYPE_DESC, "Bond", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BOND, + NM_DEVICE_IS_MASTER, TRUE, + NULL); +} + +static void +constructed (GObject *object) +{ + G_OBJECT_CLASS (nm_device_bond_parent_class)->constructed (object); + + nm_log_dbg (LOGD_HW | LOGD_BOND, "(%s): kernel ifindex %d", + nm_device_get_iface (NM_DEVICE (object)), + nm_device_get_ifindex (NM_DEVICE (object))); +} + +static void +nm_device_bond_init (NMDeviceBond * self) +{ +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GPtrArray *slaves; + GSList *list, *iter; + + switch (prop_id) { + break; + case PROP_SLAVES: + slaves = g_ptr_array_new (); + list = nm_device_master_get_slaves (NM_DEVICE (object)); + for (iter = list; iter; iter = iter->next) + g_ptr_array_add (slaves, g_strdup (nm_device_get_path (NM_DEVICE (iter->data)))); + g_slist_free (list); + g_value_take_boxed (value, slaves); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_bond_class_init (NMDeviceBondClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceBondPrivate)); + + parent_class->connection_type = NM_SETTING_BOND_SETTING_NAME; + + /* virtual methods */ + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + + parent_class->get_generic_capabilities = get_generic_capabilities; + parent_class->is_available = is_available; + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->check_connection_available = check_connection_available; + parent_class->complete_connection = complete_connection; + + parent_class->update_connection = update_connection; + + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->enslave_slave = enslave_slave; + parent_class->release_slave = release_slave; + + /* properties */ + g_object_class_install_property + (object_class, PROP_SLAVES, + g_param_spec_boxed (NM_DEVICE_BOND_SLAVES, + "Slaves", + "Slaves", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_bond_object_info); + + dbus_g_error_domain_register (NM_BOND_ERROR, NULL, NM_TYPE_BOND_ERROR); +} diff --git a/src/devices/nm-device-bond.h b/src/devices/nm-device-bond.h new file mode 100644 index 000000000..f4683ad85 --- /dev/null +++ b/src/devices/nm-device-bond.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2012 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_BOND_H +#define NM_DEVICE_BOND_H + +#include <glib-object.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_BOND (nm_device_bond_get_type ()) +#define NM_DEVICE_BOND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BOND, NMDeviceBond)) +#define NM_DEVICE_BOND_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_BOND, NMDeviceBondClass)) +#define NM_IS_DEVICE_BOND(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_BOND)) +#define NM_IS_DEVICE_BOND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_BOND)) +#define NM_DEVICE_BOND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_BOND, NMDeviceBondClass)) + +typedef enum { + NM_BOND_ERROR_CONNECTION_NOT_BOND = 0, /*< nick=ConnectionNotBond >*/ + NM_BOND_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_BOND_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMBondError; + +#define NM_DEVICE_BOND_SLAVES "slaves" + +typedef struct { + NMDevice parent; +} NMDeviceBond; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceBondClass; + + +GType nm_device_bond_get_type (void); + +NMDevice *nm_device_bond_new (NMPlatformLink *platform_device); +NMDevice *nm_device_bond_new_for_connection (NMConnection *connection); + +G_END_DECLS + +#endif /* NM_DEVICE_BOND_H */ diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c new file mode 100644 index 000000000..edc41838f --- /dev/null +++ b/src/devices/nm-device-bridge.c @@ -0,0 +1,588 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2011 - 2012 Red Hat, Inc. + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> + +#include <netinet/ether.h> +#include <stdlib.h> + +#include "gsystem-local-alloc.h" +#include "nm-device-bridge.h" +#include "nm-logging.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-dbus-glib-types.h" +#include "nm-dbus-manager.h" +#include "nm-enum-types.h" +#include "nm-platform.h" + +#include "nm-device-bridge-glue.h" + + +G_DEFINE_TYPE (NMDeviceBridge, nm_device_bridge, NM_TYPE_DEVICE) + +#define NM_DEVICE_BRIDGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridgePrivate)) + +#define NM_BRIDGE_ERROR (nm_bridge_error_quark ()) + +typedef struct { + int dummy; +} NMDeviceBridgePrivate; + +enum { + PROP_0, + PROP_SLAVES, + + LAST_PROP +}; + +/******************************************************************/ + +static GQuark +nm_bridge_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-bridge-error"); + return quark; +} + +/******************************************************************/ + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + return NM_DEVICE_CAP_CARRIER_DETECT; +} + +static gboolean +is_available (NMDevice *dev) +{ + if (NM_DEVICE_GET_CLASS (dev)->is_up) + return NM_DEVICE_GET_CLASS (dev)->is_up (dev); + return FALSE; +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + /* Connections are always available because the carrier state is determined + * by the bridge port carrier states, not the bridge's state. + */ + return TRUE; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + const char *iface; + NMSettingBridge *s_bridge; + const GByteArray *mac_address; + + if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_bridge = nm_connection_get_setting_bridge (connection); + if (!s_bridge || !nm_connection_is_type (connection, NM_SETTING_BRIDGE_SETTING_NAME)) + return FALSE; + + /* Bridge connections must specify the virtual interface name */ + iface = nm_connection_get_virtual_iface_name (connection); + if (!iface || strcmp (nm_device_get_iface (device), iface)) + return FALSE; + + mac_address = nm_setting_bridge_get_mac_address (s_bridge); + if (mac_address) { + guint hw_len; + const guint8 *hw_addr; + + hw_addr = nm_device_get_hw_address (device, &hw_len); + if ( !hw_addr + || hw_len != mac_address->len + || memcmp (mac_address->data, hw_addr, hw_len) != 0) + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMSettingBridge *s_bridge, *tmp; + guint32 i = 0; + char *name; + const GSList *iter; + gboolean found; + + nm_utils_complete_generic (connection, + NM_SETTING_BRIDGE_SETTING_NAME, + existing_connections, + _("Bridge connection %d"), + NULL, + TRUE); + + s_bridge = nm_connection_get_setting_bridge (connection); + if (!s_bridge) { + s_bridge = (NMSettingBridge *) nm_setting_bridge_new (); + nm_connection_add_setting (connection, NM_SETTING (s_bridge)); + } + + /* Grab the first name that doesn't exist in either our connections + * or a device on the system. + */ + while (i < 500 && !nm_setting_bridge_get_interface_name (s_bridge)) { + name = g_strdup_printf ("br%u", i); + /* check interface names */ + if (!nm_platform_link_exists (name)) { + /* check existing bridge connections */ + for (iter = existing_connections, found = FALSE; iter; iter = g_slist_next (iter)) { + NMConnection *candidate = iter->data; + + tmp = nm_connection_get_setting_bridge (candidate); + if (tmp && nm_connection_is_type (candidate, NM_SETTING_BRIDGE_SETTING_NAME)) { + if (g_strcmp0 (nm_setting_bridge_get_interface_name (tmp), name) == 0) { + found = TRUE; + break; + } + } + } + + if (!found) + g_object_set (G_OBJECT (s_bridge), NM_SETTING_BRIDGE_INTERFACE_NAME, name, NULL); + } + + g_free (name); + i++; + } + + return TRUE; +} + +/******************************************************************/ + +typedef struct { + const char *name; + const char *sysname; + gboolean default_if_zero; + gboolean user_hz_compensate; +} Option; + +static const Option master_options[] = { + { NM_SETTING_BRIDGE_STP, "stp_state", FALSE, FALSE }, + { NM_SETTING_BRIDGE_PRIORITY, "priority", TRUE, FALSE }, + { NM_SETTING_BRIDGE_FORWARD_DELAY, "forward_delay", TRUE, TRUE }, + { NM_SETTING_BRIDGE_HELLO_TIME, "hello_time", TRUE, TRUE }, + { NM_SETTING_BRIDGE_MAX_AGE, "max_age", TRUE, TRUE }, + { NM_SETTING_BRIDGE_AGEING_TIME, "ageing_time", TRUE, TRUE }, + { NULL, NULL } +}; + +static const Option slave_options[] = { + { NM_SETTING_BRIDGE_PORT_PRIORITY, "priority", TRUE, FALSE }, + { NM_SETTING_BRIDGE_PORT_PATH_COST, "path_cost", TRUE, FALSE }, + { NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode", FALSE, FALSE }, + { NULL, NULL } +}; + +static void +commit_option (NMDevice *device, NMSetting *setting, const Option *option, gboolean slave) +{ + int ifindex = nm_device_get_ifindex (device); + GParamSpec *pspec; + GValue val = G_VALUE_INIT; + guint32 uval = 0; + gs_free char *value = NULL; + + g_assert (setting); + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), option->name); + g_assert (pspec); + + /* Get the property's value */ + g_value_init (&val, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_object_get_property ((GObject *) setting, option->name, &val); + if (G_VALUE_HOLDS_BOOLEAN (&val)) + uval = g_value_get_boolean (&val) ? 1 : 0; + else if (G_VALUE_HOLDS_UINT (&val)) { + uval = g_value_get_uint (&val); + + /* zero means "unspecified" for some NM properties but isn't in the + * allowed kernel range, so reset the property to the default value. + */ + if (option->default_if_zero && uval == 0) { + g_value_unset (&val); + g_value_init (&val, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_param_value_set_default (pspec, &val); + uval = g_value_get_uint (&val); + } + + /* Linux kernel bridge interfaces use 'centiseconds' for time-based values. + * In reality it's not centiseconds, but depends on HZ and USER_HZ, which + * is almost always works out to be a multiplier of 100, so we can assume + * centiseconds. See clock_t_to_jiffies(). + */ + if (option->user_hz_compensate) + uval *= 100; + } else + g_assert_not_reached (); + g_value_unset (&val); + + value = g_strdup_printf ("%u", uval); + if (slave) + nm_platform_slave_set_option (ifindex, option->sysname, value); + else + nm_platform_master_set_option (ifindex, option->sysname, value); +} + +static void +commit_master_options (NMDevice *device, NMSettingBridge *setting) +{ + const Option *option; + NMSetting *s = NM_SETTING (setting); + + for (option = master_options; option->name; option++) + commit_option (device, s, option, FALSE); +} + +static void +commit_slave_options (NMDevice *device, NMSettingBridgePort *setting) +{ + const Option *option; + NMSetting *s, *s_clear = NULL; + + if (setting) + s = NM_SETTING (setting); + else + s = s_clear = nm_setting_bridge_port_new (); + + for (option = slave_options; option->name; option++) + commit_option (device, s, option, TRUE); + + g_clear_object (&s_clear); +} + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMSettingBridge *s_bridge = nm_connection_get_setting_bridge (connection); + const char *ifname = nm_device_get_iface (device); + int ifindex = nm_device_get_ifindex (device); + const Option *option; + + if (!s_bridge) { + s_bridge = (NMSettingBridge *) nm_setting_bridge_new (); + nm_connection_add_setting (connection, (NMSetting *) s_bridge); + g_object_set (s_bridge, NM_SETTING_BRIDGE_INTERFACE_NAME, ifname, NULL); + } + + for (option = master_options; option->name; option++) { + gs_free char *str = nm_platform_master_get_option (ifindex, option->sysname); + int value; + + if (str) { + value = strtol (str, NULL, 10); + + /* See comments in set_sysfs_uint() about centiseconds. */ + if (option->user_hz_compensate) + value /= 100; + + g_object_set (s_bridge, option->name, value, NULL); + } else { + nm_log_warn (LOGD_BRIDGE, "(%s): failed to read bridge setting '%s'", + nm_device_get_iface (device), option->sysname); + } + } +} + +/** + * nm_bridge_update_slave_connection: + * @slave: the slave #NMDevice, is *not* necessarily a bridge interface + * @connection: the #NMConnection to update with the bridge port settings + * + * Reads bridge port configuration and updates @connection with those + * properties. + * + * Returns: %TRUE if the port configuration was read and @connection updated, + * %FALSE if not. + */ +gboolean +nm_bridge_update_slave_connection (NMDevice *slave, NMConnection *connection) +{ + NMSettingBridgePort *s_port; + int ifindex = nm_device_get_ifindex (slave); + const Option *option; + + g_return_val_if_fail (NM_IS_DEVICE (slave), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + + s_port = nm_connection_get_setting_bridge_port (connection); + if (!s_port) { + s_port = (NMSettingBridgePort *) nm_setting_bridge_port_new (); + nm_connection_add_setting (connection, NM_SETTING (s_port)); + } + + for (option = slave_options; option->name; option++) { + gs_free char *str = nm_platform_slave_get_option (ifindex, option->sysname); + int value; + + if (str) { + value = strtol (str, NULL, 10); + + /* See comments in set_sysfs_uint() about centiseconds. */ + if (option->user_hz_compensate) + value /= 100; + + g_object_set (s_port, option->name, value, NULL); + } else { + nm_log_warn (LOGD_BRIDGE, "(%s): failed to read bridge port setting '%s'", + nm_device_get_iface (slave), option->sysname); + } + } + + return TRUE; +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason) +{ + NMActStageReturn ret; + NMConnection *connection = nm_device_get_connection (device); + + g_assert (connection); + + ret = NM_DEVICE_CLASS (nm_device_bridge_parent_class)->act_stage1_prepare (device, reason); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) + return ret; + + commit_master_options (device, nm_connection_get_setting_bridge (connection)); + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static gboolean +enslave_slave (NMDevice *device, + NMDevice *slave, + NMConnection *connection, + gboolean configure) +{ + if (configure) { + if (!nm_platform_link_enslave (nm_device_get_ip_ifindex (device), nm_device_get_ip_ifindex (slave))) + return FALSE; + + commit_slave_options (slave, nm_connection_get_setting_bridge_port (connection)); + + nm_log_info (LOGD_BRIDGE, "(%s): attached bridge port %s", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } else { + nm_log_info (LOGD_BRIDGE, "(%s): bridge port %s was attached", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } + + g_object_notify (G_OBJECT (device), NM_DEVICE_BRIDGE_SLAVES); + + return TRUE; +} + +static gboolean +release_slave (NMDevice *device, + NMDevice *slave, + gboolean configure) +{ + gboolean success = TRUE; + + if (configure) { + success = nm_platform_link_release (nm_device_get_ip_ifindex (device), + nm_device_get_ip_ifindex (slave)); + + if (success) { + nm_log_info (LOGD_BRIDGE, "(%s): detached bridge port %s", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } else { + nm_log_warn (LOGD_BRIDGE, "(%s): failed to detach bridge port %s", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } + } else { + nm_log_info (LOGD_BRIDGE, "(%s): bridge port %s was detached", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } + + g_object_notify (G_OBJECT (device), NM_DEVICE_BRIDGE_SLAVES); + return success; +} + +/******************************************************************/ + +NMDevice * +nm_device_bridge_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BRIDGE, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_DRIVER, "bridge", + NM_DEVICE_TYPE_DESC, "Bridge", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BRIDGE, + NM_DEVICE_IS_MASTER, TRUE, + NULL); +} + +NMDevice * +nm_device_bridge_new_for_connection (NMConnection *connection) +{ + const char *iface; + NMSettingBridge *s_bridge; + const GByteArray *mac_address; + + g_return_val_if_fail (connection != NULL, NULL); + + iface = nm_connection_get_virtual_iface_name (connection); + g_return_val_if_fail (iface != NULL, NULL); + + s_bridge = nm_connection_get_setting_bridge (connection); + g_return_val_if_fail (s_bridge, NULL); + + mac_address = nm_setting_bridge_get_mac_address (s_bridge); + + if ( !nm_platform_bridge_add (iface, + mac_address ? mac_address->data : NULL, + mac_address ? mac_address->len : 0) + && nm_platform_get_error () != NM_PLATFORM_ERROR_EXISTS) { + nm_log_warn (LOGD_DEVICE | LOGD_BRIDGE, "(%s): failed to create bridge master interface for '%s': %s", + iface, nm_connection_get_id (connection), + nm_platform_get_error_msg ()); + return NULL; + } + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BRIDGE, + NM_DEVICE_IFACE, iface, + NM_DEVICE_DRIVER, "bridge", + NM_DEVICE_TYPE_DESC, "Bridge", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BRIDGE, + NM_DEVICE_IS_MASTER, TRUE, + NULL); +} + +static void +constructed (GObject *object) +{ + G_OBJECT_CLASS (nm_device_bridge_parent_class)->constructed (object); + + nm_log_dbg (LOGD_HW | LOGD_BRIDGE, "(%s): kernel ifindex %d", + nm_device_get_iface (NM_DEVICE (object)), + nm_device_get_ifindex (NM_DEVICE (object))); +} + +static void +nm_device_bridge_init (NMDeviceBridge * self) +{ +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GPtrArray *slaves; + GSList *list, *iter; + + switch (prop_id) { + break; + case PROP_SLAVES: + slaves = g_ptr_array_new (); + list = nm_device_master_get_slaves (NM_DEVICE (object)); + for (iter = list; iter; iter = iter->next) + g_ptr_array_add (slaves, g_strdup (nm_device_get_path (NM_DEVICE (iter->data)))); + g_slist_free (list); + g_value_take_boxed (value, slaves); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_bridge_class_init (NMDeviceBridgeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceBridgePrivate)); + + parent_class->connection_type = NM_SETTING_BRIDGE_SETTING_NAME; + + /* virtual methods */ + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + + parent_class->get_generic_capabilities = get_generic_capabilities; + parent_class->is_available = is_available; + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->check_connection_available = check_connection_available; + parent_class->complete_connection = complete_connection; + + parent_class->update_connection = update_connection; + + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->enslave_slave = enslave_slave; + parent_class->release_slave = release_slave; + + /* properties */ + g_object_class_install_property + (object_class, PROP_SLAVES, + g_param_spec_boxed (NM_DEVICE_BRIDGE_SLAVES, + "Slaves", + "Slaves", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_bridge_object_info); + + dbus_g_error_domain_register (NM_BRIDGE_ERROR, NULL, NM_TYPE_BRIDGE_ERROR); +} diff --git a/src/devices/nm-device-bridge.h b/src/devices/nm-device-bridge.h new file mode 100644 index 000000000..4194f5a41 --- /dev/null +++ b/src/devices/nm-device-bridge.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2012 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_BRIDGE_H +#define NM_DEVICE_BRIDGE_H + +#include <glib-object.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_BRIDGE (nm_device_bridge_get_type ()) +#define NM_DEVICE_BRIDGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridge)) +#define NM_DEVICE_BRIDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridgeClass)) +#define NM_IS_DEVICE_BRIDGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_BRIDGE)) +#define NM_IS_DEVICE_BRIDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_BRIDGE)) +#define NM_DEVICE_BRIDGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridgeClass)) + +typedef enum { + NM_BRIDGE_ERROR_CONNECTION_NOT_BRIDGE = 0, /*< nick=ConnectionNotBridge >*/ + NM_BRIDGE_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_BRIDGE_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMBridgeError; + +#define NM_DEVICE_BRIDGE_SLAVES "slaves" + +typedef struct { + NMDevice parent; +} NMDeviceBridge; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceBridgeClass; + + +GType nm_device_bridge_get_type (void); + +NMDevice *nm_device_bridge_new (NMPlatformLink *platform_device); +NMDevice *nm_device_bridge_new_for_connection (NMConnection *connection); + +gboolean nm_bridge_update_slave_connection (NMDevice *slave, NMConnection *connection); + +G_END_DECLS + +#endif /* NM_DEVICE_BRIDGE_H */ diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c new file mode 100644 index 000000000..f4a90c747 --- /dev/null +++ b/src/devices/nm-device-ethernet.c @@ -0,0 +1,1770 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2005 - 2014 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "config.h" +#include <glib.h> +#include <glib/gi18n.h> +#include <netinet/in.h> +#include <string.h> +#include <stdlib.h> +#include <linux/sockios.h> +#include <linux/ethtool.h> +#include <linux/version.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <linux/if.h> +#include <errno.h> +#include <netinet/ether.h> + +#include <gudev/gudev.h> + +#include "nm-glib-compat.h" +#include "nm-device-ethernet.h" +#include "nm-device-private.h" +#include "nm-activation-request.h" +#include "NetworkManagerUtils.h" +#include "nm-supplicant-manager.h" +#include "nm-supplicant-interface.h" +#include "nm-supplicant-config.h" +#include "nm-setting-connection.h" +#include "nm-setting-wired.h" +#include "nm-setting-8021x.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-bond.h" +#include "ppp-manager/nm-ppp-manager.h" +#include "nm-logging.h" +#include "nm-utils.h" +#include "nm-enum-types.h" +#include "nm-dbus-manager.h" +#include "nm-platform.h" +#include "nm-dcb.h" +#include "nm-settings-connection.h" + +#include "nm-device-ethernet-glue.h" + + +G_DEFINE_TYPE (NMDeviceEthernet, nm_device_ethernet, NM_TYPE_DEVICE) + +#define NM_DEVICE_ETHERNET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetPrivate)) + +#define WIRED_SECRETS_TRIES "wired-secrets-tries" + +#define PPPOE_RECONNECT_DELAY 7 + +#define NM_ETHERNET_ERROR (nm_ethernet_error_quark ()) + +static NMSetting *device_get_setting (NMDevice *device, GType setting_type); + +typedef struct Supplicant { + NMSupplicantManager *mgr; + NMSupplicantInterface *iface; + + /* signal handler ids */ + guint iface_error_id; + guint iface_state_id; + + /* Timeouts and idles */ + guint iface_con_error_cb_id; + guint con_timeout_id; +} Supplicant; + +typedef enum { + DCB_WAIT_UNKNOWN = 0, + /* Ensure carrier is up before enabling DCB */ + DCB_WAIT_CARRIER_PREENABLE_UP, + /* Wait for carrier down when device starts enabling */ + DCB_WAIT_CARRIER_PRECONFIG_DOWN, + /* Wait for carrier up when device has finished enabling */ + DCB_WAIT_CARRIER_PRECONFIG_UP, + /* Wait carrier down when device starts configuring */ + DCB_WAIT_CARRIER_POSTCONFIG_DOWN, + /* Wait carrier up when device has finished configuring */ + DCB_WAIT_CARRIER_POSTCONFIG_UP, +} DcbWait; + +typedef struct { + guint8 perm_hw_addr[ETH_ALEN]; /* Permanent MAC address */ + guint8 initial_hw_addr[ETH_ALEN]; /* Initial MAC address (as seen when NM starts) */ + + guint32 speed; + + Supplicant supplicant; + guint supplicant_timeout_id; + + /* s390 */ + char * subchan1; + char * subchan2; + char * subchan3; + char * subchannels; /* Composite used for checking unmanaged specs */ + char * s390_nettype; + GHashTable * s390_options; + + /* PPPoE */ + NMPPPManager *ppp_manager; + NMIP4Config *pending_ip4_config; + gint32 last_pppoe_time; + guint pppoe_wait_id; + + /* DCB */ + DcbWait dcb_wait; + guint dcb_timeout_id; + guint dcb_carrier_id; +} NMDeviceEthernetPrivate; + +enum { + PROP_0, + PROP_PERM_HW_ADDRESS, + PROP_SPEED, + + LAST_PROP +}; + + +static GQuark +nm_ethernet_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-ethernet-error"); + return quark; +} + +static char * +get_link_basename (const char *parent_path, const char *name, GError **error) +{ + char *link_dest, *path; + char *result = NULL; + + path = g_strdup_printf ("%s/%s", parent_path, name); + link_dest = g_file_read_link (path, error); + if (link_dest) { + result = g_path_get_basename (link_dest); + g_free (link_dest); + } + g_free (path); + return result; +} + +static void +_update_s390_subchannels (NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + const char *iface; + GUdevClient *client; + GUdevDevice *dev; + GUdevDevice *parent = NULL; + const char *parent_path, *item, *driver; + const char *subsystems[] = { "net", NULL }; + GDir *dir; + GError *error = NULL; + + iface = nm_device_get_iface (NM_DEVICE (self)); + + client = g_udev_client_new (subsystems); + if (!client) { + nm_log_warn (LOGD_DEVICE | LOGD_HW, "(%s): failed to initialize GUdev client", iface); + return; + } + + dev = g_udev_client_query_by_subsystem_and_name (client, "net", iface); + if (!dev) { + nm_log_warn (LOGD_DEVICE | LOGD_HW, "(%s): failed to find device with udev", iface); + goto out; + } + + /* Try for the "ccwgroup" parent */ + parent = g_udev_device_get_parent_with_subsystem (dev, "ccwgroup", NULL); + if (!parent) { + /* FIXME: whatever 'lcs' devices' subsystem is here... */ + if (!parent) { + /* Not an s390 device */ + goto out; + } + } + + parent_path = g_udev_device_get_sysfs_path (parent); + dir = g_dir_open (parent_path, 0, &error); + if (!dir) { + nm_log_warn (LOGD_DEVICE | LOGD_HW, "(%s): failed to open directory '%s': %s", + iface, parent_path, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + goto out; + } + + while ((item = g_dir_read_name (dir))) { + if (!strcmp (item, "cdev0")) { + priv->subchan1 = get_link_basename (parent_path, "cdev0", &error); + } else if (!strcmp (item, "cdev1")) { + priv->subchan2 = get_link_basename (parent_path, "cdev1", &error); + } else if (!strcmp (item, "cdev2")) { + priv->subchan3 = get_link_basename (parent_path, "cdev2", &error); + } else if (!strcmp (item, "driver")) { + priv->s390_nettype = get_link_basename (parent_path, "driver", &error); + } else if ( !strcmp (item, "layer2") + || !strcmp (item, "portname") + || !strcmp (item, "portno")) { + char *path, *value; + path = g_strdup_printf ("%s/%s", parent_path, item); + value = nm_platform_sysctl_get (path); + if (value && *value) + g_hash_table_insert (priv->s390_options, g_strdup (item), g_strdup (value)); + else + nm_log_warn (LOGD_DEVICE | LOGD_HW, "(%s): error reading %s", iface, path); + g_free (path); + g_free (value); + } + if (error) { + nm_log_warn (LOGD_DEVICE | LOGD_HW, "(%s): %s", iface, error->message); + g_clear_error (&error); + } + } + + g_dir_close (dir); + + if (priv->subchan3) { + priv->subchannels = g_strdup_printf ("%s,%s,%s", + priv->subchan1, + priv->subchan2, + priv->subchan3); + } else if (priv->subchan2) { + priv->subchannels = g_strdup_printf ("%s,%s", + priv->subchan1, + priv->subchan2); + } else + priv->subchannels = g_strdup (priv->subchan1); + + driver = nm_device_get_driver (NM_DEVICE (self)); + nm_log_info (LOGD_DEVICE | LOGD_HW, + "(%s): found s390 '%s' subchannels [%s]", + iface, driver ? driver : "(unknown driver)", priv->subchannels); + +out: + if (parent) + g_object_unref (parent); + if (dev) + g_object_unref (dev); + g_object_unref (client); +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMDevice *self; + int ifindex; + + object = G_OBJECT_CLASS (nm_device_ethernet_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (object) { + self = NM_DEVICE (object); + ifindex = nm_device_get_ifindex (self); + + g_assert ( nm_platform_link_get_type (ifindex) == NM_LINK_TYPE_ETHERNET + || nm_platform_link_get_type (ifindex) == NM_LINK_TYPE_VETH); + + nm_log_dbg (LOGD_HW | LOGD_ETHER, "(%s): kernel ifindex %d", + nm_device_get_iface (NM_DEVICE (self)), + nm_device_get_ifindex (NM_DEVICE (self))); + + /* s390 stuff */ + _update_s390_subchannels (NM_DEVICE_ETHERNET (self)); + } + + return object; +} + +static void +clear_secrets_tries (NMDevice *device) +{ + NMActRequest *req; + NMConnection *connection; + + req = nm_device_get_act_request (device); + if (req) { + connection = nm_act_request_get_connection (req); + /* Clear wired secrets tries on success, failure, or when deactivating */ + g_object_set_data (G_OBJECT (connection), WIRED_SECRETS_TRIES, NULL); + } +} + +static void +device_state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + if ( new_state == NM_DEVICE_STATE_ACTIVATED + || new_state == NM_DEVICE_STATE_FAILED + || new_state == NM_DEVICE_STATE_DISCONNECTED) + clear_secrets_tries (device); +} + +static void +nm_device_ethernet_init (NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + priv->s390_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); +} + +NMDevice * +nm_device_ethernet_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_ETHERNET, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "Ethernet", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_ETHERNET, + NULL); +} + +static void +update_permanent_hw_address (NMDevice *dev) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (dev); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + struct ifreq req; + struct ethtool_perm_addr *epaddr = NULL; + int fd, ret; + const guint8 *mac; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_warn (LOGD_HW, "couldn't open control socket."); + return; + } + + /* Get permanent MAC address */ + memset (&req, 0, sizeof (struct ifreq)); + strncpy (req.ifr_name, nm_device_get_iface (dev), IFNAMSIZ); + + epaddr = g_malloc0 (sizeof (struct ethtool_perm_addr) + ETH_ALEN); + epaddr->cmd = ETHTOOL_GPERMADDR; + epaddr->size = ETH_ALEN; + req.ifr_data = (void *) epaddr; + + errno = 0; + ret = ioctl (fd, SIOCETHTOOL, &req); + if ((ret < 0) || !nm_ethernet_address_is_valid ((struct ether_addr *) epaddr->data)) { + nm_log_dbg (LOGD_HW | LOGD_ETHER, "(%s): unable to read permanent MAC address (error %d)", + nm_device_get_iface (dev), errno); + /* Fall back to current address */ + mac = nm_device_get_hw_address (dev, NULL); + if (mac) + memcpy (epaddr->data, mac, ETH_ALEN); + else + memset (epaddr->data, 0, ETH_ALEN); + } + + if (memcmp (&priv->perm_hw_addr, epaddr->data, ETH_ALEN)) { + memcpy (&priv->perm_hw_addr, epaddr->data, ETH_ALEN); + g_object_notify (G_OBJECT (dev), NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS); + } + + g_free (epaddr); + close (fd); +} + +static void +update_initial_hw_address (NMDevice *dev) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (dev); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + char *mac_str; + const guint8 *mac; + + /* This sets initial MAC address from current MAC address. It should only + * be called from NMDevice constructor() to really get the initial address. + */ + mac = nm_device_get_hw_address (dev, NULL); + if (mac) + memcpy (priv->initial_hw_addr, mac, ETH_ALEN); + + mac_str = nm_utils_hwaddr_ntoa (priv->initial_hw_addr, ARPHRD_ETHER); + nm_log_dbg (LOGD_DEVICE | LOGD_ETHER, "(%s): read initial MAC address %s", + nm_device_get_iface (dev), mac_str); + g_free (mac_str); +} + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + if (nm_platform_link_supports_carrier_detect (nm_device_get_ifindex (dev))) + return NM_DEVICE_CAP_CARRIER_DETECT; + else { + nm_log_info (LOGD_HW, + "(%s): driver '%s' does not support carrier detection.", + nm_device_get_iface (dev), + nm_device_get_driver (dev)); + return NM_DEVICE_CAP_NONE; + } +} + +static gboolean +match_subchans (NMDeviceEthernet *self, NMSettingWired *s_wired, gboolean *try_mac) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + const GPtrArray *subchans; + int i; + + *try_mac = TRUE; + + subchans = nm_setting_wired_get_s390_subchannels (s_wired); + if (!subchans) + return TRUE; + + /* connection requires subchannels but the device has none */ + if (!priv->subchannels) + return FALSE; + + /* Make sure each subchannel in the connection is a subchannel of this device */ + for (i = 0; i < subchans->len; i++) { + const char *candidate = g_ptr_array_index (subchans, i); + + if ( (priv->subchan1 && !strcmp (priv->subchan1, candidate)) + || (priv->subchan2 && !strcmp (priv->subchan2, candidate)) + || (priv->subchan3 && !strcmp (priv->subchan3, candidate))) + continue; + + return FALSE; /* a subchannel was not found */ + } + + *try_mac = FALSE; + return TRUE; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMSettingWired *s_wired; + + if (!NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_wired = nm_connection_get_setting_wired (connection); + + if (nm_connection_is_type (connection, NM_SETTING_PPPOE_SETTING_NAME)) { + /* NOP */ + } else if (nm_connection_is_type (connection, NM_SETTING_WIRED_SETTING_NAME)) { + if (!s_wired) + return FALSE; + } else + return FALSE; + + if (s_wired) { + const GByteArray *mac; + gboolean try_mac = TRUE; + const GSList *mac_blacklist, *mac_blacklist_iter; + + if (!match_subchans (self, s_wired, &try_mac)) + return FALSE; + + mac = nm_setting_wired_get_mac_address (s_wired); + if (try_mac && mac && memcmp (mac->data, &priv->perm_hw_addr, ETH_ALEN)) + return FALSE; + + /* Check for MAC address blacklist */ + mac_blacklist = nm_setting_wired_get_mac_address_blacklist (s_wired); + for (mac_blacklist_iter = mac_blacklist; mac_blacklist_iter; + mac_blacklist_iter = g_slist_next (mac_blacklist_iter)) { + struct ether_addr addr; + + if (!ether_aton_r (mac_blacklist_iter->data, &addr)) { + g_warn_if_reached (); + return FALSE; + } + + if (memcmp (&addr, &priv->perm_hw_addr, ETH_ALEN) == 0) + return FALSE; + } + } + + return TRUE; +} + +/* FIXME: Move it to nm-device.c and then get rid of all foo_device_get_setting() all around. + It's here now to keep the patch short. */ +static NMSetting * +device_get_setting (NMDevice *device, GType setting_type) +{ + NMActRequest *req; + NMSetting *setting = NULL; + + req = nm_device_get_act_request (device); + if (req) { + NMConnection *connection; + + connection = nm_act_request_get_connection (req); + if (connection) + setting = nm_connection_get_setting (connection, setting_type); + } + + return setting; +} + +/*****************************************************************************/ +/* 802.1X */ + +static void +remove_supplicant_timeouts (NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + + if (priv->supplicant.con_timeout_id) { + g_source_remove (priv->supplicant.con_timeout_id); + priv->supplicant.con_timeout_id = 0; + } + + if (priv->supplicant_timeout_id) { + g_source_remove (priv->supplicant_timeout_id); + priv->supplicant_timeout_id = 0; + } +} + +static void +remove_supplicant_interface_error_handler (NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + + if (priv->supplicant.iface_error_id != 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_error_id); + priv->supplicant.iface_error_id = 0; + } + + if (priv->supplicant.iface_con_error_cb_id > 0) { + g_source_remove (priv->supplicant.iface_con_error_cb_id); + priv->supplicant.iface_con_error_cb_id = 0; + } +} + +static void +supplicant_interface_release (NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + + remove_supplicant_timeouts (self); + remove_supplicant_interface_error_handler (self); + + if (priv->supplicant.iface_state_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_state_id); + priv->supplicant.iface_state_id = 0; + } + + if (priv->supplicant.iface) { + nm_supplicant_interface_disconnect (priv->supplicant.iface); + nm_supplicant_manager_iface_release (priv->supplicant.mgr, priv->supplicant.iface); + priv->supplicant.iface = NULL; + } +} + +static void +wired_secrets_cb (NMActRequest *req, + guint32 call_id, + NMConnection *connection, + GError *error, + gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + + g_return_if_fail (req == nm_device_get_act_request (dev)); + g_return_if_fail (nm_device_get_state (dev) == NM_DEVICE_STATE_NEED_AUTH); + g_return_if_fail (nm_act_request_get_connection (req) == connection); + + if (error) { + nm_log_warn (LOGD_ETHER, "%s", error->message); + nm_device_state_changed (dev, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } else + nm_device_activate_schedule_stage1_device_prepare (dev); +} + +static gboolean +link_timeout_cb (gpointer user_data) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMDevice *dev = NM_DEVICE (self); + NMActRequest *req; + NMConnection *connection; + const char *setting_name; + + priv->supplicant_timeout_id = 0; + + req = nm_device_get_act_request (dev); + + if (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED) { + nm_device_state_changed (dev, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + return FALSE; + } + + /* Disconnect event during initial authentication and credentials + * ARE checked - we are likely to have wrong key. Ask the user for + * another one. + */ + if (nm_device_get_state (dev) != NM_DEVICE_STATE_CONFIG) + goto time_out; + + connection = nm_act_request_get_connection (req); + nm_connection_clear_secrets (connection); + setting_name = nm_connection_need_secrets (connection, NULL); + if (!setting_name) + goto time_out; + + nm_log_info (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): disconnected during authentication," + " asking for new key.", + nm_device_get_iface (dev)); + supplicant_interface_release (self); + + nm_device_state_changed (dev, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + nm_act_request_get_secrets (req, + setting_name, + NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW, + NULL, + wired_secrets_cb, + self); + + return FALSE; + +time_out: + nm_log_warn (LOGD_DEVICE | LOGD_ETHER, + "(%s): link timed out.", nm_device_get_iface (dev)); + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + + return FALSE; +} + +static NMSupplicantConfig * +build_supplicant_config (NMDeviceEthernet *self) +{ + const char *con_uuid; + NMSupplicantConfig *config = NULL; + NMSetting8021x *security; + NMConnection *connection; + + connection = nm_device_get_connection (NM_DEVICE (self)); + g_assert (connection); + con_uuid = nm_connection_get_uuid (connection); + + config = nm_supplicant_config_new (); + + security = nm_connection_get_setting_802_1x (connection); + if (!nm_supplicant_config_add_setting_8021x (config, security, con_uuid, TRUE)) { + nm_log_warn (LOGD_DEVICE, "Couldn't add 802.1X security setting to supplicant config."); + g_object_unref (config); + config = NULL; + } + + return config; +} + +static void +supplicant_iface_state_cb (NMSupplicantInterface *iface, + guint32 new_state, + guint32 old_state, + int disconnect_reason, + gpointer user_data) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMDevice *device = NM_DEVICE (self); + NMSupplicantConfig *config; + gboolean success = FALSE; + NMDeviceState devstate; + + if (new_state == old_state) + return; + + nm_log_info (LOGD_DEVICE | LOGD_ETHER, + "(%s): supplicant interface state: %s -> %s", + nm_device_get_iface (device), + nm_supplicant_interface_state_to_string (old_state), + nm_supplicant_interface_state_to_string (new_state)); + + devstate = nm_device_get_state (device); + + switch (new_state) { + case NM_SUPPLICANT_INTERFACE_STATE_READY: + config = build_supplicant_config (self); + if (config) { + success = nm_supplicant_interface_set_config (priv->supplicant.iface, config); + g_object_unref (config); + + if (!success) { + nm_log_err (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): couldn't send security " + "configuration to the supplicant.", + nm_device_get_iface (device)); + } + } else { + nm_log_warn (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): couldn't build security configuration.", + nm_device_get_iface (device)); + } + + if (!success) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED); + } + break; + case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED: + remove_supplicant_interface_error_handler (self); + remove_supplicant_timeouts (self); + + /* If this is the initial association during device activation, + * schedule the next activation stage. + */ + if (devstate == NM_DEVICE_STATE_CONFIG) { + nm_log_info (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired) Stage 2 of 5 (Device Configure) successful.", + nm_device_get_iface (device)); + nm_device_activate_schedule_stage3_ip_config_start (device); + } + break; + case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED: + if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) { + /* Start the link timeout so we allow some time for reauthentication */ + if (!priv->supplicant_timeout_id) + priv->supplicant_timeout_id = g_timeout_add_seconds (15, link_timeout_cb, device); + } + break; + case NM_SUPPLICANT_INTERFACE_STATE_DOWN: + supplicant_interface_release (self); + remove_supplicant_timeouts (self); + + if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } + break; + default: + break; + } +} + +static gboolean +supplicant_iface_connection_error_cb_handler (gpointer user_data) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + + supplicant_interface_release (self); + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED); + + priv->supplicant.iface_con_error_cb_id = 0; + return FALSE; +} + +static void +supplicant_iface_connection_error_cb (NMSupplicantInterface *iface, + const char *name, + const char *message, + gpointer user_data) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + guint id; + + nm_log_warn (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): association request to the supplicant failed: %s - %s", + nm_device_get_iface (NM_DEVICE (self)), name, message); + + if (priv->supplicant.iface_con_error_cb_id) + g_source_remove (priv->supplicant.iface_con_error_cb_id); + + id = g_idle_add (supplicant_iface_connection_error_cb_handler, self); + priv->supplicant.iface_con_error_cb_id = id; +} + +static NMActStageReturn +handle_auth_or_fail (NMDeviceEthernet *self, + NMActRequest *req, + gboolean new_secrets) +{ + const char *setting_name; + guint32 tries; + NMConnection *connection; + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + tries = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (connection), WIRED_SECRETS_TRIES)); + if (tries > 3) + return NM_ACT_STAGE_RETURN_FAILURE; + + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); + + nm_connection_clear_secrets (connection); + setting_name = nm_connection_need_secrets (connection, NULL); + if (setting_name) { + NMSettingsGetSecretsFlags flags = NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + if (new_secrets) + flags |= NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW; + nm_act_request_get_secrets (req, setting_name, flags, NULL, wired_secrets_cb, self); + + g_object_set_data (G_OBJECT (connection), WIRED_SECRETS_TRIES, GUINT_TO_POINTER (++tries)); + } else { + nm_log_info (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets."); + } + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static gboolean +supplicant_connection_timeout_cb (gpointer user_data) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMDevice *device = NM_DEVICE (self); + NMActRequest *req; + NMConnection *connection; + const char *iface; + guint64 timestamp = 0; + gboolean new_secrets = TRUE; + + priv->supplicant.con_timeout_id = 0; + + iface = nm_device_get_iface (device); + + /* Authentication failed; either driver problems, the encryption key is + * wrong, the passwords or certificates were wrong or the Ethernet switch's + * port is not configured for 802.1x. */ + nm_log_warn (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): association took too long.", iface); + + supplicant_interface_release (self); + req = nm_device_get_act_request (device); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + /* Ask for new secrets only if we've never activated this connection + * before. If we've connected before, don't bother the user with dialogs, + * just retry or fail, and if we never connect the user can fix the + * password somewhere else. */ + if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), ×tamp)) + new_secrets = !timestamp; + + if (handle_auth_or_fail (self, req, new_secrets) == NM_ACT_STAGE_RETURN_POSTPONE) { + nm_log_info (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): asking for new secrets", iface); + } else + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + + return FALSE; +} + +static gboolean +supplicant_interface_init (NMDeviceEthernet *self) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + const char *iface; + + iface = nm_device_get_iface (NM_DEVICE (self)); + + /* Create supplicant interface */ + priv->supplicant.iface = nm_supplicant_manager_iface_get (priv->supplicant.mgr, iface, FALSE); + if (!priv->supplicant.iface) { + nm_log_err (LOGD_DEVICE | LOGD_ETHER, + "Couldn't initialize supplicant interface for %s.", + iface); + supplicant_interface_release (self); + return FALSE; + } + + /* Listen for it's state signals */ + priv->supplicant.iface_state_id = g_signal_connect (priv->supplicant.iface, + NM_SUPPLICANT_INTERFACE_STATE, + G_CALLBACK (supplicant_iface_state_cb), + self); + + /* Hook up error signal handler to capture association errors */ + priv->supplicant.iface_error_id = g_signal_connect (priv->supplicant.iface, + "connection-error", + G_CALLBACK (supplicant_iface_connection_error_cb), + self); + + /* Set up a timeout on the connection attempt to fail it after 25 seconds */ + priv->supplicant.con_timeout_id = g_timeout_add_seconds (25, supplicant_connection_timeout_cb, self); + + return TRUE; +} + +static gboolean +pppoe_reconnect_delay (gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + + priv->pppoe_wait_id = 0; + nm_log_info (LOGD_DEVICE, "(%s) PPPoE reconnect delay complete, resuming connection...", + nm_device_get_iface (device)); + nm_device_activate_schedule_stage2_device_config (device); + return FALSE; +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (dev); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMActRequest *req; + NMSettingWired *s_wired; + const GByteArray *cloned_mac; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + ret = NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage1_prepare (dev, reason); + if (ret == NM_ACT_STAGE_RETURN_SUCCESS) { + req = nm_device_get_act_request (NM_DEVICE (self)); + g_return_val_if_fail (req != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + s_wired = (NMSettingWired *) device_get_setting (dev, NM_TYPE_SETTING_WIRED); + if (s_wired) { + /* Set device MAC address if the connection wants to change it */ + cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired); + if (cloned_mac && (cloned_mac->len == ETH_ALEN)) + nm_device_set_hw_addr (dev, cloned_mac->data, "set", LOGD_ETHER); + } + + /* If we're re-activating a PPPoE connection a short while after + * a previous PPPoE connection was torn down, wait a bit to allow the + * remote side to handle the disconnection. Otherwise the peer may + * get confused and fail to negotiate the new connection. (rh #1023503) + */ + if (priv->last_pppoe_time) { + gint32 delay = nm_utils_get_monotonic_timestamp_s () - priv->last_pppoe_time; + + if (delay < PPPOE_RECONNECT_DELAY && device_get_setting (dev, NM_TYPE_SETTING_PPPOE)) { + nm_log_info (LOGD_DEVICE, "(%s) delaying PPPoE reconnect for %d seconds to ensure peer is ready...", + nm_device_get_iface (dev), delay); + g_assert (!priv->pppoe_wait_id); + priv->pppoe_wait_id = g_timeout_add_seconds (delay, + pppoe_reconnect_delay, + self); + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else + priv->last_pppoe_time = 0; + } + } + + return ret; +} + +static NMActStageReturn +nm_8021x_stage2_config (NMDeviceEthernet *self, NMDeviceStateReason *reason) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMConnection *connection; + NMSetting8021x *security; + const char *setting_name; + const char *iface; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + + connection = nm_device_get_connection (NM_DEVICE (self)); + g_assert (connection); + security = nm_connection_get_setting_802_1x (connection); + if (!security) { + nm_log_err (LOGD_DEVICE, "Invalid or missing 802.1X security"); + *reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return ret; + } + + if (!priv->supplicant.mgr) + priv->supplicant.mgr = nm_supplicant_manager_get (); + + iface = nm_device_get_iface (NM_DEVICE (self)); + + /* If we need secrets, get them */ + setting_name = nm_connection_need_secrets (connection, NULL); + if (setting_name) { + NMActRequest *req = nm_device_get_act_request (NM_DEVICE (self)); + + nm_log_info (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): connection '%s' has security, but secrets are required.", + iface, nm_connection_get_id (connection)); + + ret = handle_auth_or_fail (self, req, FALSE); + if (ret != NM_ACT_STAGE_RETURN_POSTPONE) + *reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + } else { + nm_log_info (LOGD_DEVICE | LOGD_ETHER, + "Activation (%s/wired): connection '%s' requires no security. No secrets needed.", + iface, nm_connection_get_id (connection)); + + if (supplicant_interface_init (self)) + ret = NM_ACT_STAGE_RETURN_POSTPONE; + else + *reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + } + + return ret; +} + +/*****************************************************************************/ +/* PPPoE */ + +static void +ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ip4_config (NMPPPManager *ppp_manager, + const char *iface, + NMIP4Config *config, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + /* Ignore PPP IP4 events that come in after initial configuration */ + if (nm_device_activate_ip4_state_in_conf (device)) { + nm_device_set_ip_iface (device, iface); + nm_device_activate_schedule_ip4_config_result (device, config); + } +} + +static NMActStageReturn +pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *reason) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMConnection *connection; + NMSettingPPPOE *s_pppoe; + NMActRequest *req; + GError *err = NULL; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + + req = nm_device_get_act_request (NM_DEVICE (self)); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (req); + + s_pppoe = nm_connection_get_setting_pppoe (connection); + g_assert (s_pppoe); + + priv->ppp_manager = nm_ppp_manager_new (nm_device_get_iface (NM_DEVICE (self))); + if (nm_ppp_manager_start (priv->ppp_manager, req, nm_setting_pppoe_get_username (s_pppoe), 30, &err)) { + g_signal_connect (priv->ppp_manager, "state-changed", + G_CALLBACK (ppp_state_changed), + self); + g_signal_connect (priv->ppp_manager, "ip4-config", + G_CALLBACK (ppp_ip4_config), + self); + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else { + nm_log_warn (LOGD_DEVICE, "(%s): PPPoE failed to start: %s", + nm_device_get_iface (NM_DEVICE (self)), err->message); + g_error_free (err); + + g_object_unref (priv->ppp_manager); + priv->ppp_manager = NULL; + + *reason = NM_DEVICE_STATE_REASON_PPP_START_FAILED; + } + + return ret; +} + +/****************************************************************/ + +static void +dcb_timeout_cleanup (NMDevice *device) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + + if (priv->dcb_timeout_id) { + g_source_remove (priv->dcb_timeout_id); + priv->dcb_timeout_id = 0; + } +} + +static void +dcb_carrier_cleanup (NMDevice *device) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + + if (priv->dcb_carrier_id) { + g_signal_handler_disconnect (device, priv->dcb_carrier_id); + priv->dcb_carrier_id = 0; + } +} + +static void dcb_state (NMDevice *device, gboolean timeout); + +static gboolean +dcb_carrier_timeout (gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + + g_return_val_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG, G_SOURCE_REMOVE); + + priv->dcb_timeout_id = 0; + if (priv->dcb_wait != DCB_WAIT_CARRIER_POSTCONFIG_DOWN) { + nm_log_warn (LOGD_DCB, + "(%s): DCB: timed out waiting for carrier (step %d)", + nm_device_get_iface (device), + priv->dcb_wait); + } + dcb_state (device, TRUE); + return G_SOURCE_REMOVE; +} + +static gboolean +dcb_configure (NMDevice *device) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + NMSettingDcb *s_dcb; + const char *iface = nm_device_get_iface (device); + GError *error = NULL; + + dcb_timeout_cleanup (device); + + s_dcb = (NMSettingDcb *) device_get_setting (device, NM_TYPE_SETTING_DCB); + g_assert (s_dcb); + if (!nm_dcb_setup (iface, s_dcb, &error)) { + nm_log_warn (LOGD_DCB, + "Activation (%s/wired) failed to enable DCB/FCoE: %s", + iface, error->message); + g_clear_error (&error); + return FALSE; + } + + /* Pause again just in case the device takes the carrier down when + * setting specific DCB attributes. + */ + nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (postconfig down)", iface); + priv->dcb_wait = DCB_WAIT_CARRIER_POSTCONFIG_DOWN; + priv->dcb_timeout_id = g_timeout_add_seconds (3, dcb_carrier_timeout, device); + return TRUE; +} + +static gboolean +dcb_enable (NMDevice *device) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + const char *iface = nm_device_get_iface (device); + GError *error = NULL; + + dcb_timeout_cleanup (device); + if (!nm_dcb_enable (iface, TRUE, &error)) { + nm_log_warn (LOGD_DCB, + "Activation (%s/wired) failed to enable DCB/FCoE: %s", + iface, error->message); + g_clear_error (&error); + return FALSE; + } + + /* Pause for 3 seconds after enabling DCB to let the card reconfigure + * itself. Drivers will often re-initialize internal settings which + * takes the carrier down for 2 or more seconds. During this time, + * lldpad will refuse to do anything else with the card since the carrier + * is down. But NM might get the carrier-down signal long after calling + * "dcbtool dcb on", so we have to first wait for the carrier to go down. + */ + nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (preconfig down)", iface); + priv->dcb_wait = DCB_WAIT_CARRIER_PRECONFIG_DOWN; + priv->dcb_timeout_id = g_timeout_add_seconds (3, dcb_carrier_timeout, device); + return TRUE; +} + +static void +dcb_state (NMDevice *device, gboolean timeout) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + const char *iface = nm_device_get_iface (device); + gboolean carrier; + + g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG); + + + carrier = nm_platform_link_is_connected (nm_device_get_ifindex (device)); + nm_log_dbg (LOGD_DCB, "(%s): dcb_state() wait %d carrier %d timeout %d", iface, priv->dcb_wait, carrier, timeout); + + switch (priv->dcb_wait) { + case DCB_WAIT_CARRIER_PREENABLE_UP: + if (timeout || carrier) { + nm_log_dbg (LOGD_DCB, "(%s): dcb_state() enabling DCB", iface); + dcb_timeout_cleanup (device); + if (!dcb_enable (device)) { + dcb_carrier_cleanup (device); + nm_device_state_changed (device, + NM_ACT_STAGE_RETURN_FAILURE, + NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED); + } + } + break; + case DCB_WAIT_CARRIER_PRECONFIG_DOWN: + dcb_timeout_cleanup (device); + priv->dcb_wait = DCB_WAIT_CARRIER_PRECONFIG_UP; + + if (!carrier) { + /* Wait for the carrier to come back up */ + nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (preconfig up)", iface); + priv->dcb_timeout_id = g_timeout_add_seconds (5, dcb_carrier_timeout, device); + break; + } + nm_log_dbg (LOGD_DCB, "(%s): dcb_state() preconfig down falling through", iface); + /* carrier never went down? fall through */ + case DCB_WAIT_CARRIER_PRECONFIG_UP: + if (timeout || carrier) { + nm_log_dbg (LOGD_DCB, "(%s): dcb_state() preconfig up configuring DCB", iface); + dcb_timeout_cleanup (device); + if (!dcb_configure (device)) { + dcb_carrier_cleanup (device); + nm_device_state_changed (device, + NM_ACT_STAGE_RETURN_FAILURE, + NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED); + } + } + break; + case DCB_WAIT_CARRIER_POSTCONFIG_DOWN: + dcb_timeout_cleanup (device); + priv->dcb_wait = DCB_WAIT_CARRIER_POSTCONFIG_UP; + + if (!carrier) { + /* Wait for the carrier to come back up */ + nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (postconfig up)", iface); + priv->dcb_timeout_id = g_timeout_add_seconds (5, dcb_carrier_timeout, device); + break; + } + nm_log_dbg (LOGD_DCB, "(%s): dcb_state() postconfig down falling through", iface); + /* carrier never went down? fall through */ + case DCB_WAIT_CARRIER_POSTCONFIG_UP: + if (timeout || carrier) { + nm_log_dbg (LOGD_DCB, "(%s): dcb_state() postconfig up starting IP", iface); + dcb_timeout_cleanup (device); + dcb_carrier_cleanup (device); + priv->dcb_wait = DCB_WAIT_UNKNOWN; + nm_device_activate_schedule_stage3_ip_config_start (device); + } + break; + default: + g_assert_not_reached (); + } +} + +static void +dcb_carrier_changed (NMDevice *device, GParamSpec *pspec, gpointer unused) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + + g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG); + + if (priv->dcb_timeout_id) { + nm_log_dbg (LOGD_DCB, "(%s): carrier_changed() calling dcb_state()", nm_device_get_iface (device)); + dcb_state (device, FALSE); + } +} + +/****************************************************************/ + +static NMActStageReturn +act_stage2_config (NMDevice *device, NMDeviceStateReason *reason) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + NMSettingConnection *s_con; + const char *connection_type; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; + NMSettingDcb *s_dcb; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + s_con = NM_SETTING_CONNECTION (device_get_setting (device, NM_TYPE_SETTING_CONNECTION)); + g_assert (s_con); + + dcb_timeout_cleanup (device); + dcb_carrier_cleanup (device); + + /* 802.1x has to run before any IP configuration since the 802.1x auth + * process opens the port up for normal traffic. + */ + connection_type = nm_setting_connection_get_connection_type (s_con); + if (!strcmp (connection_type, NM_SETTING_WIRED_SETTING_NAME)) { + NMSetting8021x *security; + + security = (NMSetting8021x *) device_get_setting (device, NM_TYPE_SETTING_802_1X); + if (security) { + /* FIXME: for now 802.1x is mutually exclusive with DCB */ + return nm_8021x_stage2_config (NM_DEVICE_ETHERNET (device), reason); + } + } + + /* DCB and FCoE setup */ + s_dcb = (NMSettingDcb *) device_get_setting (device, NM_TYPE_SETTING_DCB); + if (s_dcb) { + /* lldpad really really wants the carrier to be up */ + if (nm_platform_link_is_connected (nm_device_get_ifindex (device))) { + if (!dcb_enable (device)) { + *reason = NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + } else { + nm_log_dbg (LOGD_DCB, "(%s): waiting for carrier (preenable up)", + nm_device_get_iface (device)); + priv->dcb_wait = DCB_WAIT_CARRIER_PREENABLE_UP; + priv->dcb_timeout_id = g_timeout_add_seconds (4, dcb_carrier_timeout, device); + } + + /* Watch carrier independently of NMDeviceClass::carrier_changed so + * we get instant notifications of disconnection that aren't deferred. + */ + priv->dcb_carrier_id = g_signal_connect (device, + "notify::" NM_DEVICE_CARRIER, + G_CALLBACK (dcb_carrier_changed), + NULL); + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } + + return ret; +} + +static NMActStageReturn +act_stage3_ip4_config_start (NMDevice *device, + NMIP4Config **out_config, + NMDeviceStateReason *reason) +{ + NMSettingConnection *s_con; + const char *connection_type; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + s_con = NM_SETTING_CONNECTION (device_get_setting (device, NM_TYPE_SETTING_CONNECTION)); + g_assert (s_con); + + connection_type = nm_setting_connection_get_connection_type (s_con); + if (!strcmp (connection_type, NM_SETTING_PPPOE_SETTING_NAME)) + return pppoe_stage3_ip4_config_start (NM_DEVICE_ETHERNET (device), reason); + + return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage3_ip4_config_start (device, out_config, reason); +} + +static void +ip4_config_pre_commit (NMDevice *device, NMIP4Config *config) +{ + NMConnection *connection; + NMSettingWired *s_wired; + guint32 mtu; + + /* MTU only set for plain ethernet */ + if (NM_DEVICE_ETHERNET_GET_PRIVATE (device)->ppp_manager) + return; + + connection = nm_device_get_connection (device); + g_assert (connection); + s_wired = nm_connection_get_setting_wired (connection); + g_assert (s_wired); + + /* MTU override */ + mtu = nm_setting_wired_get_mtu (s_wired); + if (mtu) + nm_ip4_config_set_mtu (config, mtu); +} + +static void +deactivate (NMDevice *device) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + NMSettingDcb *s_dcb; + GError *error = NULL; + + /* Clear wired secrets tries when deactivating */ + clear_secrets_tries (device); + + if (priv->pppoe_wait_id) { + g_source_remove (priv->pppoe_wait_id); + priv->pppoe_wait_id = 0; + } + + if (priv->pending_ip4_config) { + g_object_unref (priv->pending_ip4_config); + priv->pending_ip4_config = NULL; + } + + if (priv->ppp_manager) { + g_object_unref (priv->ppp_manager); + priv->ppp_manager = NULL; + } + + supplicant_interface_release (self); + + priv->dcb_wait = DCB_WAIT_UNKNOWN; + dcb_timeout_cleanup (device); + dcb_carrier_cleanup (device); + + /* Tear down DCB/FCoE if it was enabled */ + s_dcb = (NMSettingDcb *) device_get_setting (device, NM_TYPE_SETTING_DCB); + if (s_dcb) { + if (!nm_dcb_cleanup (nm_device_get_iface (device), &error)) { + nm_log_warn (LOGD_DEVICE | LOGD_HW, + "(%s) failed to disable DCB/FCoE: %s", + nm_device_get_iface (device), error->message); + g_clear_error (&error); + } + } + + /* Set last PPPoE connection time */ + if (device_get_setting (device, NM_TYPE_SETTING_PPPOE)) + NM_DEVICE_ETHERNET_GET_PRIVATE (device)->last_pppoe_time = nm_utils_get_monotonic_timestamp_s (); + + /* Reset MAC address back to initial address */ + nm_device_set_hw_addr (device, priv->initial_hw_addr, "reset", LOGD_ETHER); +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + NMSettingWired *s_wired; + NMSettingPPPOE *s_pppoe; + const GByteArray *setting_mac; + + s_pppoe = nm_connection_get_setting_pppoe (connection); + + /* We can't telepathically figure out the service name or username, so if + * those weren't given, we can't complete the connection. + */ + if (s_pppoe && !nm_setting_verify (NM_SETTING (s_pppoe), NULL, error)) + return FALSE; + + /* Default to an ethernet-only connection, but if a PPPoE setting was given + * then PPPoE should be our connection type. + */ + nm_utils_complete_generic (connection, + s_pppoe ? NM_SETTING_PPPOE_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME, + existing_connections, + s_pppoe ? _("PPPoE connection %d") : _("Wired connection %d"), + NULL, + s_pppoe ? FALSE : TRUE); /* No IPv6 by default yet for PPPoE */ + + s_wired = nm_connection_get_setting_wired (connection); + if (!s_wired) { + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + } + + setting_mac = nm_setting_wired_get_mac_address (s_wired); + if (setting_mac) { + /* Make sure the setting MAC (if any) matches the device's permanent MAC */ + if (memcmp (setting_mac->data, priv->perm_hw_addr, ETH_ALEN)) { + g_set_error_literal (error, + NM_SETTING_WIRED_ERROR, + NM_SETTING_WIRED_ERROR_INVALID_PROPERTY, + NM_SETTING_WIRED_MAC_ADDRESS); + return FALSE; + } + } else { + GByteArray *mac; + const guint8 null_mac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + + /* Lock the connection to this device by default */ + if (memcmp (priv->perm_hw_addr, null_mac, ETH_ALEN)) { + mac = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (mac, priv->perm_hw_addr, ETH_ALEN); + g_object_set (G_OBJECT (s_wired), NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL); + g_byte_array_free (mac, TRUE); + } + } + + return TRUE; +} + +static gboolean +spec_match_list (NMDevice *device, const GSList *specs) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + + if (priv->subchannels && nm_match_spec_s390_subchannels (specs, priv->subchannels)) + return TRUE; + + return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->spec_match_list (device, specs); +} + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + NMSettingWired *s_wired = nm_connection_get_setting_wired (connection); + guint maclen; + const guint8 *mac = nm_device_get_hw_address (device, &maclen); + static const guint8 null_mac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + const char *mac_prop = NM_SETTING_WIRED_MAC_ADDRESS; + GByteArray *array; + GHashTableIter iter; + gpointer key, value; + + if (!s_wired) { + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, (NMSetting *) s_wired); + } + + /* If the device reports a permanent address, use that for the MAC address + * and the current MAC, if different, is the cloned MAC. + */ + if (memcmp (priv->perm_hw_addr, null_mac, ETH_ALEN)) { + array = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (array, priv->perm_hw_addr, ETH_ALEN); + g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, array, NULL); + g_byte_array_unref (array); + + mac_prop = NULL; + if (mac && memcmp (priv->perm_hw_addr, mac, ETH_ALEN)) + mac_prop = NM_SETTING_WIRED_CLONED_MAC_ADDRESS; + } + + if (mac_prop && mac && maclen == ETH_ALEN) { + array = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (array, (guint8 *) mac, maclen); + g_object_set (s_wired, mac_prop, array, NULL); + g_byte_array_unref (array); + } + + /* We don't set the MTU as we don't know whether it was set explicitly */ + + /* s390 */ + if (priv->subchannels) { + GPtrArray *subchan_arr = g_ptr_array_sized_new (3); + if (priv->subchan1) + g_ptr_array_add (subchan_arr, priv->subchan1); + if (priv->subchan2) + g_ptr_array_add (subchan_arr, priv->subchan2); + if (priv->subchan3) + g_ptr_array_add (subchan_arr, priv->subchan3); + g_object_set (s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, subchan_arr, NULL); + g_ptr_array_free (subchan_arr, TRUE); + } + if (priv->s390_nettype) + g_object_set (s_wired, NM_SETTING_WIRED_S390_NETTYPE, priv->s390_nettype, NULL); + g_hash_table_iter_init (&iter, priv->s390_options); + while (g_hash_table_iter_next (&iter, &key, &value)) { + nm_setting_wired_add_s390_option (s_wired, (const char *) key, (const char *) value); + } + +} + +static void +get_link_speed (NMDevice *device) +{ + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device); + struct ifreq ifr; + struct ethtool_cmd edata = { + .cmd = ETHTOOL_GSET, + }; + guint32 speed; + int fd; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_warn (LOGD_HW | LOGD_ETHER, "couldn't open ethtool control socket."); + return; + } + + memset (&ifr, 0, sizeof (struct ifreq)); + strncpy (ifr.ifr_name, nm_device_get_iface (device), IFNAMSIZ); + ifr.ifr_data = (char *) &edata; + + if (ioctl (fd, SIOCETHTOOL, &ifr) < 0) { + close (fd); + return; + } + close (fd); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + speed = edata.speed; +#else + speed = ethtool_cmd_speed (&edata); +#endif + if (speed == G_MAXUINT16 || speed == G_MAXUINT32) + speed = 0; + + if (priv->speed == speed) + return; + + priv->speed = speed; + g_object_notify (G_OBJECT (device), "speed"); + + nm_log_dbg (LOGD_HW | LOGD_ETHER, "(%s): speed is now %d Mb/s", + nm_device_get_iface (device), speed); +} + +static void +carrier_changed (NMDevice *device, gboolean carrier) +{ + if (carrier) + get_link_speed (device); + + NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->carrier_changed (device, carrier); +} + +static void +dispose (GObject *object) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (object); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + + if (priv->pppoe_wait_id) { + g_source_remove (priv->pppoe_wait_id); + priv->pppoe_wait_id = 0; + } + + dcb_timeout_cleanup (NM_DEVICE (self)); + dcb_carrier_cleanup (NM_DEVICE (self)); + + G_OBJECT_CLASS (nm_device_ethernet_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (object); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + + g_clear_object (&priv->supplicant.mgr); + g_free (priv->subchan1); + g_free (priv->subchan2); + g_free (priv->subchan3); + g_free (priv->subchannels); + g_free (priv->s390_nettype); + g_hash_table_destroy (priv->s390_options); + + G_OBJECT_CLASS (nm_device_ethernet_parent_class)->finalize (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceEthernet *self = NM_DEVICE_ETHERNET (object); + NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + + switch (prop_id) { + case PROP_PERM_HW_ADDRESS: + g_value_take_string (value, nm_utils_hwaddr_ntoa (&priv->perm_hw_addr, ARPHRD_ETHER)); + break; + case PROP_SPEED: + g_value_set_uint (value, priv->speed); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_ethernet_class_init (NMDeviceEthernetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceEthernetPrivate)); + + parent_class->connection_type = NM_SETTING_WIRED_SETTING_NAME; + + /* virtual methods */ + object_class->constructor = constructor; + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + parent_class->get_generic_capabilities = get_generic_capabilities; + parent_class->update_permanent_hw_address = update_permanent_hw_address; + parent_class->update_initial_hw_address = update_initial_hw_address; + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->complete_connection = complete_connection; + + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->act_stage2_config = act_stage2_config; + parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start; + parent_class->ip4_config_pre_commit = ip4_config_pre_commit; + parent_class->deactivate = deactivate; + parent_class->spec_match_list = spec_match_list; + parent_class->update_connection = update_connection; + parent_class->carrier_changed = carrier_changed; + + parent_class->state_changed = device_state_changed; + + /* properties */ + g_object_class_install_property + (object_class, PROP_PERM_HW_ADDRESS, + g_param_spec_string (NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS, + "Permanent MAC Address", + "Permanent hardware MAC address", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_SPEED, + g_param_spec_uint (NM_DEVICE_ETHERNET_SPEED, + "Speed", + "Speed", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_ethernet_object_info); + + dbus_g_error_domain_register (NM_ETHERNET_ERROR, NULL, NM_TYPE_ETHERNET_ERROR); +} diff --git a/src/devices/nm-device-ethernet.h b/src/devices/nm-device-ethernet.h new file mode 100644 index 000000000..bcaf270a0 --- /dev/null +++ b/src/devices/nm-device-ethernet.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2005 - 2010 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef NM_DEVICE_ETHERNET_H +#define NM_DEVICE_ETHERNET_H + +#include <glib-object.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_ETHERNET (nm_device_ethernet_get_type ()) +#define NM_DEVICE_ETHERNET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernet)) +#define NM_DEVICE_ETHERNET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass)) +#define NM_IS_DEVICE_ETHERNET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_ETHERNET)) +#define NM_IS_DEVICE_ETHERNET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_ETHERNET)) +#define NM_DEVICE_ETHERNET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass)) + +typedef enum +{ + NM_ETHERNET_ERROR_CONNECTION_NOT_WIRED = 0, /*< nick=ConnectionNotWired >*/ + NM_ETHERNET_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_ETHERNET_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMEthernetError; + +#define NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS "perm-hw-address" +#define NM_DEVICE_ETHERNET_SPEED "speed" + +typedef struct { + NMDevice parent; +} NMDeviceEthernet; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceEthernetClass; + + +GType nm_device_ethernet_get_type (void); + + +NMDevice *nm_device_ethernet_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_ETHERNET_H */ diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c new file mode 100644 index 000000000..fc4d1c248 --- /dev/null +++ b/src/devices/nm-device-factory.c @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "nm-device-factory.h" + +enum { + DEVICE_ADDED, + COMPONENT_ADDED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +gboolean +nm_device_factory_emit_component_added (NMDeviceFactory *factory, GObject *component) +{ + gboolean consumed = FALSE; + + g_signal_emit (factory, signals[COMPONENT_ADDED], 0, component, &consumed); + return consumed; +} + +static void +interface_init (gpointer g_iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + static gboolean initialized = FALSE; + + if (G_LIKELY (initialized)) + return; + + /* Signals */ + signals[DEVICE_ADDED] = g_signal_new (NM_DEVICE_FACTORY_DEVICE_ADDED, + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceFactory, device_added), + NULL, NULL, NULL, + G_TYPE_NONE, 1, NM_TYPE_DEVICE); + + signals[COMPONENT_ADDED] = g_signal_new (NM_DEVICE_FACTORY_COMPONENT_ADDED, + iface_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMDeviceFactory, component_added), + g_signal_accumulator_true_handled, NULL, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT); + + initialized = TRUE; +} + +GType +nm_device_factory_get_type (void) +{ + static GType device_factory_type = 0; + + if (!device_factory_type) { + const GTypeInfo device_factory_info = { + sizeof (NMDeviceFactory), /* class_size */ + interface_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + device_factory_type = g_type_register_static (G_TYPE_INTERFACE, + "NMDeviceFactory", + &device_factory_info, + 0); + g_type_interface_add_prerequisite (device_factory_type, G_TYPE_OBJECT); + } + + return device_factory_type; +} + +NMDevice * +nm_device_factory_new_link (NMDeviceFactory *factory, + NMPlatformLink *plink, + GError **error) +{ + g_return_val_if_fail (factory != NULL, NULL); + g_return_val_if_fail (plink != NULL, NULL); + + if (NM_DEVICE_FACTORY_GET_INTERFACE (factory)->new_link) + return NM_DEVICE_FACTORY_GET_INTERFACE (factory)->new_link (factory, plink, error); + return NULL; +} + diff --git a/src/devices/nm-device-factory.h b/src/devices/nm-device-factory.h new file mode 100644 index 000000000..f0e3dc197 --- /dev/null +++ b/src/devices/nm-device-factory.h @@ -0,0 +1,136 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2014 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_FACTORY_H +#define NM_DEVICE_FACTORY_H + +#include <glib.h> +#include <glib-object.h> + +#include "NetworkManager.h" +#include "nm-platform.h" +#include "nm-device.h" + +/* WARNING: this file is private API between NetworkManager and its internal + * device plugins. Its API can change at any time and is not guaranteed to be + * stable. NM and device plugins are distributed together and this API is + * not meant to enable third-party plugins. + */ + +typedef struct _NMDeviceFactory NMDeviceFactory; + +/** + * nm_device_factory_create: + * @error: an error if creation of the factory failed, or %NULL + * + * Creates a #GObject that implements the #NMDeviceFactory interface. This + * function must not emit any signals or perform any actions that would cause + * devices or components to be created immediately. Instead these should be + * deferred to an idle handler. + * + * Returns: the #GObject implementing #NMDeviceFactory or %NULL + */ +NMDeviceFactory *nm_device_factory_create (GError **error); + +/* Should match nm_device_factory_create() */ +typedef NMDeviceFactory * (*NMDeviceFactoryCreateFunc) (GError **error); + +/** + * nm_device_factory_get_device_type: + * + * Returns: the #NMDeviceType that this plugin creates + */ +NMDeviceType nm_device_factory_get_device_type (void); + +/* Should match nm_device_factory_get_device_type() */ +typedef NMDeviceType (*NMDeviceFactoryDeviceTypeFunc) (void); + +/********************************************************************/ + +#define NM_TYPE_DEVICE_FACTORY (nm_device_factory_get_type ()) +#define NM_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_FACTORY, NMDeviceFactory)) +#define NM_IS_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_FACTORY)) +#define NM_DEVICE_FACTORY_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), NM_TYPE_DEVICE_FACTORY, NMDeviceFactory)) + +/* signals */ +#define NM_DEVICE_FACTORY_COMPONENT_ADDED "component-added" +#define NM_DEVICE_FACTORY_DEVICE_ADDED "device-added" + +struct _NMDeviceFactory { + GTypeInterface g_iface; + + /** + * new_link: + * @factory: the #NMDeviceFactory + * @link: the new link + * @error: error if the link could be claimed but an error occurred + * + * The NetworkManager core was notified of a new link which the plugin + * may want to claim and create a #NMDevice subclass for. If the link + * represents a device the factory is capable of claiming, but the device + * could not be created, %NULL should be returned and @error should be set. + * %NULL should always be returned and @error should never be set if the + * factory cannot create devices for the type which @link represents. + * + * Returns: the #NMDevice if the link was claimed and created, %NULL if not + */ + NMDevice * (*new_link) (NMDeviceFactory *factory, + NMPlatformLink *plink, + GError **error); + + /* Signals */ + + /** + * device_added: + * @factory: the #NMDeviceFactory + * @device: the new #NMDevice subclass + * + * The factory emits this signal if it finds a new device by itself. + */ + void (*device_added) (NMDeviceFactory *factory, NMDevice *device); + + /** + * component_added: + * @factory: the #NMDeviceFactory + * @component: a new component which existing devices may wish to claim + * + * The factory emits this signal when it finds a new component. For example, + * the WWAN factory may indicate that a new modem is available, which an + * existing Bluetooth device may wish to claim. If no device claims the + * component, the plugin is allowed to create a new #NMDevice instance for + * that component and emit the "device-added" signal. + * + * Returns: %TRUE if the component was claimed by a device, %FALSE if not + */ + gboolean (*component_added) (NMDeviceFactory *factory, GObject *component); +}; + +GType nm_device_factory_get_type (void); + +NMDevice * nm_device_factory_new_link (NMDeviceFactory *factory, + NMPlatformLink *plink, + GError **error); + +/* For use by implementations */ +gboolean nm_device_factory_emit_component_added (NMDeviceFactory *factory, + GObject *component); + +#endif /* NM_DEVICE_FACTORY_H */ + diff --git a/src/devices/nm-device-generic.c b/src/devices/nm-device-generic.c new file mode 100644 index 000000000..5261c1333 --- /dev/null +++ b/src/devices/nm-device-generic.c @@ -0,0 +1,217 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#include "config.h" + +#include "nm-device-generic.h" +#include "nm-device-private.h" +#include "nm-enum-types.h" +#include "nm-platform.h" +#include "nm-utils.h" +#include "nm-glib-compat.h" +#include "nm-dbus-manager.h" + +#include "nm-device-generic-glue.h" + +G_DEFINE_TYPE (NMDeviceGeneric, nm_device_generic, NM_TYPE_DEVICE) + +#define NM_DEVICE_GENERIC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_GENERIC, NMDeviceGenericPrivate)) + +typedef struct { + char *type_description; +} NMDeviceGenericPrivate; + +enum { + PROP_0, + PROP_TYPE_DESCRIPTION, + + LAST_PROP +}; + +#define NM_DEVICE_GENERIC_ERROR (nm_device_generic_error_quark ()) + +static GQuark +nm_device_generic_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-device-generic-error"); + return quark; +} + +/**************************************************************/ + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + if (nm_platform_link_supports_carrier_detect (nm_device_get_ifindex (dev))) + return NM_DEVICE_CAP_CARRIER_DETECT; + else + return NM_DEVICE_CAP_NONE; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMSettingConnection *s_con; + + if (!NM_DEVICE_CLASS (nm_device_generic_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + if (!nm_connection_is_type (connection, NM_SETTING_GENERIC_SETTING_NAME)) + return FALSE; + + s_con = nm_connection_get_setting_connection (connection); + if (!nm_setting_connection_get_interface_name (s_con)) + return FALSE; + + return TRUE; +} + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMSettingConnection *s_con; + + if (!nm_connection_get_setting_generic (connection)) + nm_connection_add_setting (connection, nm_setting_generic_new ()); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_object_set (G_OBJECT (s_con), + NM_SETTING_CONNECTION_INTERFACE_NAME, nm_device_get_iface (device), + NULL); +} + +/**************************************************************/ + +NMDevice * +nm_device_generic_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_GENERIC, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "Generic", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_GENERIC, + NULL); +} + +static void +nm_device_generic_init (NMDeviceGeneric *self) +{ + nm_device_set_initial_unmanaged_flag (NM_DEVICE (self), NM_UNMANAGED_DEFAULT, TRUE); +} + +static void +constructed (GObject *object) +{ + NMDeviceGeneric *self = NM_DEVICE_GENERIC (object); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE (self); + + if (!priv->type_description) { + int ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self)); + + if (ifindex != 0) + priv->type_description = g_strdup (nm_platform_link_get_type_name (ifindex)); + } + + G_OBJECT_CLASS (nm_device_generic_parent_class)->constructed (object); +} + +static void +dispose (GObject *object) +{ + NMDeviceGeneric *self = NM_DEVICE_GENERIC (object); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE (self); + + g_clear_pointer (&priv->type_description, g_free); + + G_OBJECT_CLASS (nm_device_generic_parent_class)->dispose (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceGeneric *self = NM_DEVICE_GENERIC (object); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE (self); + + switch (prop_id) { + case PROP_TYPE_DESCRIPTION: + g_value_set_string (value, priv->type_description); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMDeviceGeneric *self = NM_DEVICE_GENERIC (object); + NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE (self); + + switch (prop_id) { + case PROP_TYPE_DESCRIPTION: + priv->type_description = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_generic_class_init (NMDeviceGenericClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMDeviceGenericPrivate)); + + parent_class->connection_type = NM_SETTING_GENERIC_SETTING_NAME; + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + parent_class->get_generic_capabilities = get_generic_capabilities; + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->update_connection = update_connection; + + /* properties */ + g_object_class_install_property + (object_class, PROP_TYPE_DESCRIPTION, + g_param_spec_string (NM_DEVICE_GENERIC_TYPE_DESCRIPTION, + "Type Description", + "Type description", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_generic_object_info); + + dbus_g_error_domain_register (NM_DEVICE_GENERIC_ERROR, NULL, NM_TYPE_DEVICE_GENERIC_ERROR); +} diff --git a/src/devices/nm-device-generic.h b/src/devices/nm-device-generic.h new file mode 100644 index 000000000..e7b7090b1 --- /dev/null +++ b/src/devices/nm-device-generic.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_GENERIC_H +#define NM_DEVICE_GENERIC_H + +#include <glib-object.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_GENERIC (nm_device_generic_get_type ()) +#define NM_DEVICE_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_GENERIC, NMDeviceGeneric)) +#define NM_DEVICE_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_GENERIC, NMDeviceGenericClass)) +#define NM_IS_DEVICE_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_GENERIC)) +#define NM_IS_DEVICE_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_GENERIC)) +#define NM_DEVICE_GENERIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_GENERIC, NMDeviceGenericClass)) + +typedef enum +{ + NM_DEVICE_GENERIC_ERROR_CONNECTION_NOT_GENERIC = 0, /*< nick=ConnectionNotGeneric >*/ + NM_DEVICE_GENERIC_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_DEVICE_GENERIC_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMDeviceGenericError; + +#define NM_DEVICE_GENERIC_TYPE_DESCRIPTION "type-description" + +typedef struct { + NMDevice parent; +} NMDeviceGeneric; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceGenericClass; + +GType nm_device_generic_get_type (void); + +NMDevice *nm_device_generic_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_GENERIC_H */ diff --git a/src/devices/nm-device-gre.c b/src/devices/nm-device-gre.c new file mode 100644 index 000000000..0412e9931 --- /dev/null +++ b/src/devices/nm-device-gre.c @@ -0,0 +1,278 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#include "config.h" + +#include <string.h> + +#include "nm-device-gre.h" +#include "nm-device-private.h" +#include "nm-dbus-manager.h" +#include "nm-logging.h" +#include "nm-manager.h" +#include "nm-platform.h" + +#include "nm-device-gre-glue.h" + +G_DEFINE_TYPE (NMDeviceGre, nm_device_gre, NM_TYPE_DEVICE_GENERIC) + +#define NM_DEVICE_GRE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_GRE, NMDeviceGrePrivate)) + +typedef struct { + NMPlatformGreProperties props; +} NMDeviceGrePrivate; + +enum { + PROP_0, + PROP_PARENT, + PROP_INPUT_FLAGS, + PROP_OUTPUT_FLAGS, + PROP_INPUT_KEY, + PROP_OUTPUT_KEY, + PROP_LOCAL, + PROP_REMOTE, + PROP_TTL, + PROP_TOS, + PROP_PATH_MTU_DISCOVERY, + + LAST_PROP +}; + +/**************************************************************/ + +static void +update_properties (NMDevice *device) +{ + NMDeviceGrePrivate *priv = NM_DEVICE_GRE_GET_PRIVATE (device); + GObject *object = G_OBJECT (device); + NMPlatformGreProperties props; + + if (!nm_platform_gre_get_properties (nm_device_get_ifindex (device), &props)) { + nm_log_warn (LOGD_HW, "(%s): could not read gre properties", + nm_device_get_iface (device)); + return; + } + + g_object_freeze_notify (object); + + if (priv->props.parent_ifindex != props.parent_ifindex) + g_object_notify (object, NM_DEVICE_GRE_PARENT); + if (priv->props.input_flags != props.input_flags) + g_object_notify (object, NM_DEVICE_GRE_INPUT_FLAGS); + if (priv->props.output_flags != props.output_flags) + g_object_notify (object, NM_DEVICE_GRE_OUTPUT_FLAGS); + if (priv->props.input_key != props.input_key) + g_object_notify (object, NM_DEVICE_GRE_INPUT_KEY); + if (priv->props.output_key != props.output_key) + g_object_notify (object, NM_DEVICE_GRE_OUTPUT_KEY); + if (priv->props.local != props.local) + g_object_notify (object, NM_DEVICE_GRE_LOCAL); + if (priv->props.remote != props.remote) + g_object_notify (object, NM_DEVICE_GRE_REMOTE); + if (priv->props.ttl != props.ttl) + g_object_notify (object, NM_DEVICE_GRE_TTL); + if (priv->props.tos != props.tos) + g_object_notify (object, NM_DEVICE_GRE_TOS); + if (priv->props.path_mtu_discovery != props.path_mtu_discovery) + g_object_notify (object, NM_DEVICE_GRE_PATH_MTU_DISCOVERY); + + memcpy (&priv->props, &props, sizeof (NMPlatformGreProperties)); + + g_object_thaw_notify (object); +} + +static void +link_changed (NMDevice *device, NMPlatformLink *info) +{ + NM_DEVICE_CLASS (nm_device_gre_parent_class)->link_changed (device, info); + update_properties (device); +} + +/**************************************************************/ + +NMDevice * +nm_device_gre_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_GRE, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "Gre", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_GENERIC, + NULL); +} + +static void +nm_device_gre_init (NMDeviceGre *self) +{ +} + +static void +constructed (GObject *object) +{ + update_properties (NM_DEVICE (object)); + + G_OBJECT_CLASS (nm_device_gre_parent_class)->constructed (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceGrePrivate *priv = NM_DEVICE_GRE_GET_PRIVATE (object); + char buf[INET_ADDRSTRLEN]; + NMDevice *parent; + + switch (prop_id) { + case PROP_PARENT: + parent = nm_manager_get_device_by_ifindex (nm_manager_get (), priv->props.parent_ifindex); + g_value_set_boxed (value, parent ? nm_device_get_path (parent) : "/"); + break; + case PROP_INPUT_FLAGS: + g_value_set_uint (value, priv->props.input_flags); + break; + case PROP_OUTPUT_FLAGS: + g_value_set_uint (value, priv->props.output_flags); + break; + case PROP_INPUT_KEY: + g_value_set_uint (value, priv->props.input_key); + break; + case PROP_OUTPUT_KEY: + g_value_set_uint (value, priv->props.output_key); + break; + case PROP_LOCAL: + g_value_set_string (value, inet_ntop (AF_INET, &priv->props.local, buf, sizeof (buf))); + break; + case PROP_REMOTE: + g_value_set_string (value, inet_ntop (AF_INET, &priv->props.remote, buf, sizeof (buf))); + break; + case PROP_TTL: + g_value_set_uchar (value, priv->props.ttl); + break; + case PROP_TOS: + g_value_set_uchar (value, priv->props.tos); + break; + case PROP_PATH_MTU_DISCOVERY: + g_value_set_boolean (value, priv->props.path_mtu_discovery); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_gre_class_init (NMDeviceGreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMDeviceGrePrivate)); + + object_class->constructed = constructed; + object_class->get_property = get_property; + + device_class->link_changed = link_changed; + + /* properties */ + g_object_class_install_property + (object_class, PROP_PARENT, + g_param_spec_boxed (NM_DEVICE_GRE_PARENT, + "Parent", + "Parent device", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_INPUT_FLAGS, + g_param_spec_uint (NM_DEVICE_GRE_INPUT_FLAGS, + "Input flags", + "Input flags", + 0, G_MAXUINT16, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_OUTPUT_FLAGS, + g_param_spec_uint (NM_DEVICE_GRE_OUTPUT_FLAGS, + "Output flags", + "Output flags", + 0, G_MAXUINT16, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_INPUT_KEY, + g_param_spec_uint (NM_DEVICE_GRE_INPUT_KEY, + "Input key", + "Input key", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_OUTPUT_KEY, + g_param_spec_uint (NM_DEVICE_GRE_OUTPUT_KEY, + "Output key", + "Output key", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_LOCAL, + g_param_spec_string (NM_DEVICE_GRE_LOCAL, + "Local", + "Local", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_REMOTE, + g_param_spec_string (NM_DEVICE_GRE_REMOTE, + "Remote", + "Remote", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_TTL, + g_param_spec_uchar (NM_DEVICE_GRE_TTL, + "TTL", + "TTL", + 0, 255, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_TOS, + g_param_spec_uchar (NM_DEVICE_GRE_TOS, + "ToS", + "ToS", + 0, 255, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_PATH_MTU_DISCOVERY, + g_param_spec_boolean (NM_DEVICE_GRE_PATH_MTU_DISCOVERY, + "Path MTU Discovery", + "Path MTU Discovery", + FALSE, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_gre_object_info); +} diff --git a/src/devices/nm-device-gre.h b/src/devices/nm-device-gre.h new file mode 100644 index 000000000..610b38050 --- /dev/null +++ b/src/devices/nm-device-gre.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_GRE_H +#define NM_DEVICE_GRE_H + +#include <glib-object.h> + +#include "nm-device-generic.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_GRE (nm_device_gre_get_type ()) +#define NM_DEVICE_GRE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_GRE, NMDeviceGre)) +#define NM_DEVICE_GRE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_GRE, NMDeviceGreClass)) +#define NM_IS_DEVICE_GRE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_GRE)) +#define NM_IS_DEVICE_GRE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_GRE)) +#define NM_DEVICE_GRE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_GRE, NMDeviceGreClass)) + +#define NM_DEVICE_GRE_PARENT "parent" +#define NM_DEVICE_GRE_INPUT_FLAGS "input-flags" +#define NM_DEVICE_GRE_OUTPUT_FLAGS "output-flags" +#define NM_DEVICE_GRE_INPUT_KEY "input-key" +#define NM_DEVICE_GRE_OUTPUT_KEY "output-key" +#define NM_DEVICE_GRE_LOCAL "local" +#define NM_DEVICE_GRE_REMOTE "remote" +#define NM_DEVICE_GRE_TTL "ttl" +#define NM_DEVICE_GRE_TOS "tos" +#define NM_DEVICE_GRE_PATH_MTU_DISCOVERY "path-mtu-discovery" + +typedef struct { + NMDeviceGeneric parent; +} NMDeviceGre; + +typedef struct { + NMDeviceGenericClass parent; + +} NMDeviceGreClass; + +GType nm_device_gre_get_type (void); + +NMDevice *nm_device_gre_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_GRE_H */ diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c new file mode 100644 index 000000000..8ec79157f --- /dev/null +++ b/src/devices/nm-device-infiniband.c @@ -0,0 +1,412 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2011 Red Hat, Inc. + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> + +#include <linux/if_infiniband.h> +#include <netinet/ether.h> + +#include "nm-device-infiniband.h" +#include "nm-logging.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-enum-types.h" +#include "nm-dbus-manager.h" + +#include "nm-device-infiniband-glue.h" + + +G_DEFINE_TYPE (NMDeviceInfiniband, nm_device_infiniband, NM_TYPE_DEVICE) + +#define NM_DEVICE_INFINIBAND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfinibandPrivate)) + +#define NM_INFINIBAND_ERROR (nm_infiniband_error_quark ()) + +typedef struct { + int dummy; +} NMDeviceInfinibandPrivate; + +enum { + PROP_0, + + LAST_PROP +}; + +static GQuark +nm_infiniband_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-infiniband-error"); + return quark; +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMDevice *self; + + object = G_OBJECT_CLASS (nm_device_infiniband_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + self = NM_DEVICE (object); + + nm_log_dbg (LOGD_HW | LOGD_INFINIBAND, "(%s): kernel ifindex %d", + nm_device_get_iface (self), + nm_device_get_ifindex (self)); + return object; +} + +static void +nm_device_infiniband_init (NMDeviceInfiniband * self) +{ +} + +NMDevice * +nm_device_infiniband_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_INFINIBAND, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "InfiniBand", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_INFINIBAND, + NULL); +} + +NMDevice * +nm_device_infiniband_new_partition (NMConnection *connection, + NMDevice *parent) +{ + NMSettingInfiniband *s_infiniband; + int p_key, parent_ifindex; + const char *iface; + + g_return_val_if_fail (connection != NULL, NULL); + g_return_val_if_fail (NM_IS_DEVICE_INFINIBAND (parent), NULL); + + iface = nm_connection_get_virtual_iface_name (connection); + g_return_val_if_fail (iface != NULL, NULL); + + parent_ifindex = nm_device_get_ifindex (parent); + s_infiniband = nm_connection_get_setting_infiniband (connection); + p_key = nm_setting_infiniband_get_p_key (s_infiniband); + + if ( !nm_platform_infiniband_partition_add (parent_ifindex, p_key) + && nm_platform_get_error () != NM_PLATFORM_ERROR_EXISTS) { + nm_log_warn (LOGD_DEVICE | LOGD_INFINIBAND, "(%s): failed to add InfiniBand P_Key interface for '%s': %s", + iface, nm_connection_get_id (connection), + nm_platform_get_error_msg ()); + return NULL; + } + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_INFINIBAND, + NM_DEVICE_IFACE, iface, + NM_DEVICE_DRIVER, nm_device_get_driver (parent), + NM_DEVICE_TYPE_DESC, "InfiniBand", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_INFINIBAND, + NULL); +} + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + return NM_DEVICE_CAP_CARRIER_DETECT; +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMActStageReturn ret; + NMActRequest *req; + NMConnection *connection; + NMSettingInfiniband *s_infiniband; + const char *transport_mode; + char *mode_path; + gboolean ok; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + ret = NM_DEVICE_CLASS (nm_device_infiniband_parent_class)->act_stage1_prepare (dev, reason); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) + return ret; + + req = nm_device_get_act_request (dev); + g_return_val_if_fail (req != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + s_infiniband = nm_connection_get_setting_infiniband (connection); + g_assert (s_infiniband); + + transport_mode = nm_setting_infiniband_get_transport_mode (s_infiniband); + + mode_path = g_strdup_printf ("/sys/class/net/%s/mode", + ASSERT_VALID_PATH_COMPONENT (nm_device_get_iface (dev))); + if (!g_file_test (mode_path, G_FILE_TEST_EXISTS)) { + g_free (mode_path); + + if (!strcmp (transport_mode, "datagram")) + return NM_ACT_STAGE_RETURN_SUCCESS; + else { + *reason = NM_DEVICE_STATE_REASON_INFINIBAND_MODE; + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + + ok = nm_platform_sysctl_set (mode_path, transport_mode); + g_free (mode_path); + + if (!ok) { + *reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +ip4_config_pre_commit (NMDevice *self, NMIP4Config *config) +{ + NMConnection *connection; + NMSettingInfiniband *s_infiniband; + guint32 mtu; + + connection = nm_device_get_connection (self); + g_assert (connection); + s_infiniband = nm_connection_get_setting_infiniband (connection); + g_assert (s_infiniband); + + /* MTU override */ + mtu = nm_setting_infiniband_get_mtu (s_infiniband); + if (mtu) + nm_ip4_config_set_mtu (config, mtu); +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMSettingInfiniband *s_infiniband; + const GByteArray *mac; + + if (!NM_DEVICE_CLASS (nm_device_infiniband_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + if (!nm_connection_is_type (connection, NM_SETTING_INFINIBAND_SETTING_NAME)) + return FALSE; + + s_infiniband = nm_connection_get_setting_infiniband (connection); + if (!s_infiniband) + return FALSE; + + if (s_infiniband) { + mac = nm_setting_infiniband_get_mac_address (s_infiniband); + /* We only compare the last 8 bytes */ + if (mac && memcmp (mac->data + INFINIBAND_ALEN - 8, + nm_device_get_hw_address (device, NULL) + INFINIBAND_ALEN - 8, + 8)) + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMSettingInfiniband *s_infiniband; + const GByteArray *setting_mac; + const guint8 *hw_address; + + nm_utils_complete_generic (connection, + NM_SETTING_INFINIBAND_SETTING_NAME, + existing_connections, + _("InfiniBand connection %d"), + NULL, + TRUE); + + s_infiniband = nm_connection_get_setting_infiniband (connection); + if (!s_infiniband) { + s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new (); + nm_connection_add_setting (connection, NM_SETTING (s_infiniband)); + } + + setting_mac = nm_setting_infiniband_get_mac_address (s_infiniband); + hw_address = nm_device_get_hw_address (device, NULL); + if (setting_mac) { + /* Make sure the setting MAC (if any) matches the device's MAC */ + if (memcmp (setting_mac->data, hw_address, INFINIBAND_ALEN)) { + g_set_error_literal (error, + NM_SETTING_INFINIBAND_ERROR, + NM_SETTING_INFINIBAND_ERROR_INVALID_PROPERTY, + NM_SETTING_INFINIBAND_MAC_ADDRESS); + return FALSE; + } + } else { + GByteArray *mac; + + /* Lock the connection to this device by default */ + mac = g_byte_array_sized_new (INFINIBAND_ALEN); + g_byte_array_append (mac, hw_address, INFINIBAND_ALEN); + g_object_set (G_OBJECT (s_infiniband), NM_SETTING_INFINIBAND_MAC_ADDRESS, mac, NULL); + g_byte_array_free (mac, TRUE); + } + + if (!nm_setting_infiniband_get_transport_mode (s_infiniband)) + g_object_set (G_OBJECT (s_infiniband), NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL); + + return TRUE; +} + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMSettingInfiniband *s_infiniband = nm_connection_get_setting_infiniband (connection); + guint maclen; + gconstpointer mac = nm_device_get_hw_address (device, &maclen); + static const guint8 null_mac[INFINIBAND_ALEN] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + GByteArray *array; + char *mode_path, *contents = NULL; + const char *transport_mode = "datagram"; + + if (!s_infiniband) { + s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new (); + nm_connection_add_setting (connection, (NMSetting *) s_infiniband); + } + + if (mac && (maclen == INFINIBAND_ALEN) && (memcmp (mac, null_mac, maclen) != 0)) { + array = g_byte_array_sized_new (maclen); + g_byte_array_append (array, (guint8 *) mac, maclen); + g_object_set (s_infiniband, NM_SETTING_INFINIBAND_MAC_ADDRESS, array, NULL); + g_byte_array_unref (array); + } + + mode_path = g_strdup_printf ("/sys/class/net/%s/mode", + ASSERT_VALID_PATH_COMPONENT (nm_device_get_iface (device))); + contents = nm_platform_sysctl_get (mode_path); + g_free (mode_path); + if (contents) { + if (strstr (contents, "datagram")) + transport_mode = "datagram"; + else if (strstr (contents, "connected")) + transport_mode = "connected"; + g_free (contents); + } + g_object_set (G_OBJECT (s_infiniband), NM_SETTING_INFINIBAND_TRANSPORT_MODE, transport_mode, NULL); +} + +static gboolean +spec_match_list (NMDevice *device, const GSList *specs) +{ + char *hwaddr_str, *spec_str; + const GSList *iter; + + if (NM_DEVICE_CLASS (nm_device_infiniband_parent_class)->spec_match_list (device, specs)) + return TRUE; + + hwaddr_str = nm_utils_hwaddr_ntoa (nm_device_get_hw_address (device, NULL), + ARPHRD_INFINIBAND); + + /* InfiniBand hardware address matches only need to match the last + * 8 bytes. In string format, that means we skip the first 36 + * characters of hwaddr_str, and the first 40 of the spec (to skip + * "mac:" too). + */ + for (iter = specs; iter; iter = g_slist_next (iter)) { + spec_str = iter->data; + + if ( !g_ascii_strncasecmp (spec_str, "mac:", 4) + && strlen (spec_str) > 40 + && !g_ascii_strcasecmp (spec_str + 40, hwaddr_str + 36)) { + g_free (hwaddr_str); + return TRUE; + } + } + + g_free (hwaddr_str); + return FALSE; +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_infiniband_class_init (NMDeviceInfinibandClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceInfinibandPrivate)); + + /* virtual methods */ + object_class->constructor = constructor; + object_class->get_property = get_property; + object_class->set_property = set_property; + + parent_class->get_generic_capabilities = get_generic_capabilities; + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->complete_connection = complete_connection; + parent_class->update_connection = update_connection; + parent_class->spec_match_list = spec_match_list; + + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->ip4_config_pre_commit = ip4_config_pre_commit; + + /* properties */ + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_infiniband_object_info); + + dbus_g_error_domain_register (NM_INFINIBAND_ERROR, NULL, NM_TYPE_INFINIBAND_ERROR); +} diff --git a/src/devices/nm-device-infiniband.h b/src/devices/nm-device-infiniband.h new file mode 100644 index 000000000..45dc1ab6a --- /dev/null +++ b/src/devices/nm-device-infiniband.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2011 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_INFINIBAND_H +#define NM_DEVICE_INFINIBAND_H + +#include <glib-object.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_INFINIBAND (nm_device_infiniband_get_type ()) +#define NM_DEVICE_INFINIBAND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfiniband)) +#define NM_DEVICE_INFINIBAND_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfinibandClass)) +#define NM_IS_DEVICE_INFINIBAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_INFINIBAND)) +#define NM_IS_DEVICE_INFINIBAND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_INFINIBAND)) +#define NM_DEVICE_INFINIBAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfinibandClass)) + +typedef enum { + NM_INFINIBAND_ERROR_CONNECTION_NOT_INFINIBAND = 0, /*< nick=ConnectionNotInfiniband >*/ + NM_INFINIBAND_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_INFINIBAND_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMInfinibandError; + +typedef struct { + NMDevice parent; +} NMDeviceInfiniband; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceInfinibandClass; + + +GType nm_device_infiniband_get_type (void); + +NMDevice *nm_device_infiniband_new (NMPlatformLink *platform_device); +NMDevice *nm_device_infiniband_new_partition (NMConnection *connection, + NMDevice *parent); + +G_END_DECLS + +#endif /* NM_DEVICE_INFINIBAND_H */ diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c new file mode 100644 index 000000000..22848fe78 --- /dev/null +++ b/src/devices/nm-device-macvlan.c @@ -0,0 +1,181 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#include "config.h" + +#include <string.h> + +#include "nm-device-macvlan.h" +#include "nm-device-private.h" +#include "nm-dbus-manager.h" +#include "nm-logging.h" +#include "nm-manager.h" +#include "nm-platform.h" + +#include "nm-device-macvlan-glue.h" + +G_DEFINE_TYPE (NMDeviceMacvlan, nm_device_macvlan, NM_TYPE_DEVICE_GENERIC) + +#define NM_DEVICE_MACVLAN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlanPrivate)) + +typedef struct { + NMPlatformMacvlanProperties props; +} NMDeviceMacvlanPrivate; + +enum { + PROP_0, + PROP_PARENT, + PROP_MODE, + PROP_NO_PROMISC, + + LAST_PROP +}; + +/**************************************************************/ + +/**************************************************************/ + +static void +update_properties (NMDevice *device) +{ + NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (device); + GObject *object = G_OBJECT (device); + NMPlatformMacvlanProperties props; + + if (!nm_platform_macvlan_get_properties (nm_device_get_ifindex (device), &props)) { + nm_log_warn (LOGD_HW, "(%s): could not read macvlan properties", + nm_device_get_iface (device)); + return; + } + + g_object_freeze_notify (object); + + if (priv->props.parent_ifindex != props.parent_ifindex) + g_object_notify (object, NM_DEVICE_MACVLAN_PARENT); + if (g_strcmp0 (priv->props.mode, props.mode) != 0) + g_object_notify (object, NM_DEVICE_MACVLAN_MODE); + if (priv->props.no_promisc != props.no_promisc) + g_object_notify (object, NM_DEVICE_MACVLAN_NO_PROMISC); + + memcpy (&priv->props, &props, sizeof (NMPlatformMacvlanProperties)); + + g_object_thaw_notify (object); +} + +static void +link_changed (NMDevice *device, NMPlatformLink *info) +{ + NM_DEVICE_CLASS (nm_device_macvlan_parent_class)->link_changed (device, info); + update_properties (device); +} + +/**************************************************************/ + +NMDevice * +nm_device_macvlan_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_MACVLAN, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "Macvlan", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_GENERIC, + NULL); +} + +static void +nm_device_macvlan_init (NMDeviceMacvlan *self) +{ +} + +static void +constructed (GObject *object) +{ + update_properties (NM_DEVICE (object)); + + G_OBJECT_CLASS (nm_device_macvlan_parent_class)->constructed (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (object); + NMDevice *parent; + + switch (prop_id) { + case PROP_PARENT: + parent = nm_manager_get_device_by_ifindex (nm_manager_get (), priv->props.parent_ifindex); + g_value_set_boxed (value, parent ? nm_device_get_path (parent) : "/"); + break; + case PROP_MODE: + g_value_set_string (value, priv->props.mode); + break; + case PROP_NO_PROMISC: + g_value_set_boolean (value, priv->props.no_promisc); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMDeviceMacvlanPrivate)); + + object_class->constructed = constructed; + object_class->get_property = get_property; + + device_class->link_changed = link_changed; + + /* properties */ + g_object_class_install_property + (object_class, PROP_PARENT, + g_param_spec_boxed (NM_DEVICE_MACVLAN_PARENT, + "Parent", + "Parent device", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_MODE, + g_param_spec_string (NM_DEVICE_MACVLAN_MODE, + "Mode", + "Mode: 'private', 'vepa', 'bridge', or 'passthru'", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_NO_PROMISC, + g_param_spec_boolean (NM_DEVICE_MACVLAN_NO_PROMISC, + "No-promisc", + "No promiscuous mode", + FALSE, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_macvlan_object_info); +} diff --git a/src/devices/nm-device-macvlan.h b/src/devices/nm-device-macvlan.h new file mode 100644 index 000000000..348ed2f96 --- /dev/null +++ b/src/devices/nm-device-macvlan.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_MACVLAN_H +#define NM_DEVICE_MACVLAN_H + +#include <glib-object.h> + +#include "nm-device-generic.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_MACVLAN (nm_device_macvlan_get_type ()) +#define NM_DEVICE_MACVLAN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlan)) +#define NM_DEVICE_MACVLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlanClass)) +#define NM_IS_DEVICE_MACVLAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_MACVLAN)) +#define NM_IS_DEVICE_MACVLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_MACVLAN)) +#define NM_DEVICE_MACVLAN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlanClass)) + +#define NM_DEVICE_MACVLAN_PARENT "parent" +#define NM_DEVICE_MACVLAN_MODE "mode" +#define NM_DEVICE_MACVLAN_NO_PROMISC "no-promisc" + +typedef struct { + NMDeviceGeneric parent; +} NMDeviceMacvlan; + +typedef struct { + NMDeviceGenericClass parent; + +} NMDeviceMacvlanClass; + +GType nm_device_macvlan_get_type (void); + +NMDevice *nm_device_macvlan_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_MACVLAN_H */ diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h new file mode 100644 index 000000000..24bb0b336 --- /dev/null +++ b/src/devices/nm-device-private.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2011 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_PRIVATE_H +#define NM_DEVICE_PRIVATE_H + +#include "nm-device.h" + +/* This file should only be used by subclasses of NMDevice */ + +#define NM_DEVICE_PLATFORM_DEVICE "platform-device" + +enum NMActStageReturn { + NM_ACT_STAGE_RETURN_FAILURE = 0, + NM_ACT_STAGE_RETURN_SUCCESS, /* Activation stage done */ + NM_ACT_STAGE_RETURN_POSTPONE, /* Long-running operation in progress */ + NM_ACT_STAGE_RETURN_WAIT, /* Not ready to start stage; wait */ + NM_ACT_STAGE_RETURN_STOP /* Activation stage done; nothing to do */ +}; + +#define NM_DEVICE_CAP_NONSTANDARD_CARRIER 0x80000000 + +#define NM_DEVICE_CAP_INTERNAL_MASK 0x80000000 + +void nm_device_set_ip_iface (NMDevice *self, const char *iface); + +void nm_device_activate_schedule_stage3_ip_config_start (NMDevice *device); + +gboolean nm_device_activate_stage3_ip4_start (NMDevice *self); + +gboolean nm_device_activate_stage3_ip6_start (NMDevice *self); + +gboolean nm_device_bring_up (NMDevice *self, gboolean wait, gboolean *no_firmware); + +void nm_device_take_down (NMDevice *self, gboolean block); + +gboolean nm_device_update_hw_address (NMDevice *self); +gboolean nm_device_set_hw_addr (NMDevice *device, const guint8 *addr, + const char *detail, guint64 hw_log_domain); + +gboolean nm_device_ip_config_should_fail (NMDevice *self, gboolean ip6); + +void nm_device_set_firmware_missing (NMDevice *self, gboolean missing); + +void nm_device_activate_schedule_stage1_device_prepare (NMDevice *device); +void nm_device_activate_schedule_stage2_device_config (NMDevice *device); + +void nm_device_activate_schedule_ip4_config_result(NMDevice *device, NMIP4Config *config); +void nm_device_activate_schedule_ip4_config_timeout (NMDevice *device); + +void nm_device_activate_schedule_ip6_config_result (NMDevice *device); +void nm_device_activate_schedule_ip6_config_timeout (NMDevice *device); + +gboolean nm_device_activate_ip4_state_in_conf (NMDevice *device); +gboolean nm_device_activate_ip4_state_in_wait (NMDevice *device); + +gboolean nm_device_activate_ip6_state_in_conf (NMDevice *device); +gboolean nm_device_activate_ip6_state_in_wait (NMDevice *device); + +void nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout); +void nm_device_set_dhcp_anycast_address (NMDevice *device, guint8 *addr); + +gboolean nm_device_dhcp4_renew (NMDevice *device, gboolean release); +gboolean nm_device_dhcp6_renew (NMDevice *device, gboolean release); + +void nm_device_recheck_available_connections (NMDevice *device); + +void nm_device_queued_state_clear (NMDevice *device); + +NMDeviceState nm_device_queued_state_peek (NMDevice *device); + +gboolean nm_device_get_enslaved (NMDevice *device); + +NMDevice *nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex); + +void nm_device_master_check_slave_physical_port (NMDevice *dev, NMDevice *slave, + guint64 log_domain); + +void nm_device_set_carrier (NMDevice *device, gboolean carrier); + +void nm_device_emit_recheck_auto_activate (NMDevice *device); +void nm_device_queue_recheck_assume (NMDevice *device); + +#endif /* NM_DEVICE_PRIVATE_H */ diff --git a/src/devices/nm-device-team.c b/src/devices/nm-device-team.c new file mode 100644 index 000000000..f3b25e3b1 --- /dev/null +++ b/src/devices/nm-device-team.c @@ -0,0 +1,894 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <sys/types.h> +#include <unistd.h> +#include <signal.h> +#include <sys/wait.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <netinet/ether.h> +#if WITH_TEAMDCTL +#include <teamdctl.h> +#endif +#include <stdlib.h> + +#include "nm-device-team.h" +#include "nm-logging.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-platform.h" +#include "nm-dbus-glib-types.h" +#include "nm-dbus-manager.h" +#include "nm-enum-types.h" +#include "nm-posix-signals.h" + +#include "nm-device-team-glue.h" + + +G_DEFINE_TYPE (NMDeviceTeam, nm_device_team, NM_TYPE_DEVICE) + +#define NM_DEVICE_TEAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_TEAM, NMDeviceTeamPrivate)) + +#define NM_TEAM_ERROR (nm_team_error_quark ()) + +static gboolean teamd_start (NMDevice *dev, NMSettingTeam *s_team); + +typedef struct { +#if WITH_TEAMDCTL + struct teamdctl *tdc; +#endif + GPid teamd_pid; + guint teamd_process_watch; + guint teamd_timeout; + guint teamd_dbus_watch; +} NMDeviceTeamPrivate; + +enum { + PROP_0, + PROP_SLAVES, + + LAST_PROP +}; + +/******************************************************************/ + +static GQuark +nm_team_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-team-error"); + return quark; +} + +/******************************************************************/ + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + return NM_DEVICE_CAP_CARRIER_DETECT; +} + +static gboolean +is_available (NMDevice *dev) +{ + if (NM_DEVICE_GET_CLASS (dev)->is_up) + return NM_DEVICE_GET_CLASS (dev)->is_up (dev); + return FALSE; +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + /* Connections are always available because the carrier state is determined + * by the team port carrier states, not the team's state. + */ + return TRUE; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + const char *iface; + NMSettingTeam *s_team; + + if (!NM_DEVICE_CLASS (nm_device_team_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_team = nm_connection_get_setting_team (connection); + if (!s_team || !nm_connection_is_type (connection, NM_SETTING_TEAM_SETTING_NAME)) + return FALSE; + + /* Team connections must specify the virtual interface name */ + iface = nm_connection_get_virtual_iface_name (connection); + if (!iface || strcmp (nm_device_get_iface (device), iface)) + return FALSE; + + /* FIXME: match team properties like mode, etc? */ + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMSettingTeam *s_team, *tmp; + guint32 i = 0; + char *name; + const GSList *iter; + gboolean found; + + nm_utils_complete_generic (connection, + NM_SETTING_TEAM_SETTING_NAME, + existing_connections, + _("Team connection %d"), + NULL, + TRUE); + + s_team = nm_connection_get_setting_team (connection); + if (!s_team) { + s_team = (NMSettingTeam *) nm_setting_team_new (); + nm_connection_add_setting (connection, NM_SETTING (s_team)); + } + + /* Grab the first name that doesn't exist in either our connections + * or a device on the system. + */ + while (i < 500 && !nm_setting_team_get_interface_name (s_team)) { + name = g_strdup_printf ("team%u", i); + /* check interface names */ + if (!nm_platform_link_exists (name)) { + /* check existing team connections */ + for (iter = existing_connections, found = FALSE; iter; iter = g_slist_next (iter)) { + NMConnection *candidate = iter->data; + + tmp = nm_connection_get_setting_team (candidate); + if (tmp && nm_connection_is_type (candidate, NM_SETTING_TEAM_SETTING_NAME)) { + if (g_strcmp0 (nm_setting_team_get_interface_name (tmp), name) == 0) { + found = TRUE; + break; + } + } + } + + if (!found) + g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_INTERFACE_NAME, name, NULL); + } + + g_free (name); + i++; + } + + return TRUE; +} + +#if WITH_TEAMDCTL +static gboolean +ensure_teamd_connection (NMDevice *self) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self); + int err; + + if (priv->tdc) + return TRUE; + + priv->tdc = teamdctl_alloc (); + g_assert (priv->tdc); + err = teamdctl_connect (priv->tdc, nm_device_get_iface (self), NULL, NULL); + if (err != 0) { + nm_log_err (LOGD_TEAM, "(%s): failed to connect to teamd (err=%d)", + nm_device_get_iface (self), err); + teamdctl_free (priv->tdc); + priv->tdc = NULL; + } + + return !!priv->tdc; +} +#endif + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMSettingTeam *s_team = nm_connection_get_setting_team (connection); + const char *iface = nm_device_get_iface (device); + + if (!s_team) { + s_team = (NMSettingTeam *) nm_setting_team_new (); + nm_connection_add_setting (connection, (NMSetting *) s_team); + g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_INTERFACE_NAME, iface, NULL); + } + g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_CONFIG, NULL, NULL); + +#if WITH_TEAMDCTL + teamd_start (device, s_team); + if (NM_DEVICE_TEAM_GET_PRIVATE (device)->teamd_pid > 0 && ensure_teamd_connection (device)) { + const char *config = NULL; + int err; + + err = teamdctl_config_get_raw_direct (NM_DEVICE_TEAM_GET_PRIVATE (device)->tdc, + (char **)&config); + if (err == 0) + g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_CONFIG, config, NULL); + else + nm_log_err (LOGD_TEAM, "(%s): failed to read teamd config (err=%d)", iface, err); + } +#endif +} + +/******************************************************************/ + +gboolean +nm_team_update_slave_connection (NMDevice *slave, NMConnection *connection) +{ + NMSettingTeamPort *s_port; + const char *iface = nm_device_get_iface (slave); + char *port_config = NULL; + gboolean with_teamdctl = FALSE; + int err = 0; +#if WITH_TEAMDCTL + const char *master_iface; + int master_ifindex; + struct teamdctl *tdc; + const char *team_port_config = NULL; +#endif + + g_return_val_if_fail (NM_IS_DEVICE (slave), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + +#if WITH_TEAMDCTL + master_ifindex = nm_platform_link_get_master (nm_device_get_ifindex (slave)); + g_assert (master_ifindex > 0); + master_iface = nm_platform_link_get_name (master_ifindex); + g_assert (master_iface); + + tdc = teamdctl_alloc (); + g_assert (tdc); + err = teamdctl_connect (tdc, master_iface, NULL, NULL); + if (err) { + nm_log_err (LOGD_TEAM, "(%s): failed to connect to teamd for master %s (err=%d)", + iface, master_iface, err); + teamdctl_free (tdc); + return FALSE; + } + err = teamdctl_port_config_get_raw_direct (tdc, iface, (char **)&team_port_config); + port_config = g_strdup (team_port_config); + teamdctl_free (tdc); + with_teamdctl = TRUE; +#endif + + s_port = nm_connection_get_setting_team_port (connection); + if (!s_port) { + s_port = (NMSettingTeamPort *) nm_setting_team_port_new (); + nm_connection_add_setting (connection, NM_SETTING (s_port)); + } + + g_object_set (G_OBJECT (s_port), NM_SETTING_TEAM_PORT_CONFIG, port_config, NULL); + g_free (port_config); + + if (!with_teamdctl || err != 0) { + if (!with_teamdctl) + nm_log_err (LOGD_TEAM, "(%s): failed to read teamd port configuration " + " (compiled without libteamdctl support)", iface); + else + nm_log_err (LOGD_TEAM, "(%s): failed to read teamd port configuration (err=%d)", + iface, err); + return FALSE; + } + + return TRUE; +} + +/******************************************************************/ + +static gboolean +ensure_killed (gpointer data) +{ + int pid = GPOINTER_TO_INT (data); + + if (kill (pid, 0) == 0) + kill (pid, SIGKILL); + + /* ensure the child is reaped */ + nm_log_dbg (LOGD_TEAM, "waiting for teamd pid %d to exit", pid); + waitpid (pid, NULL, 0); + nm_log_dbg (LOGD_TEAM, "teamd pid %d cleaned up", pid); + + return FALSE; +} + +static void +service_kill (int pid) +{ + if (kill (pid, SIGTERM) == 0) + g_timeout_add_seconds (2, ensure_killed, GINT_TO_POINTER (pid)); + else { + kill (pid, SIGKILL); + + /* ensure the child is reaped */ + nm_log_dbg (LOGD_TEAM, "waiting for teamd pid %d to exit", pid); + waitpid (pid, NULL, 0); + nm_log_dbg (LOGD_TEAM, "teamd pid %d cleaned up", pid); + } +} + +static void +teamd_timeout_remove (NMDevice *dev) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + + if (priv->teamd_timeout) { + g_source_remove (priv->teamd_timeout); + priv->teamd_timeout = 0; + } +} + +static void +teamd_cleanup (NMDevice *dev, gboolean device_state_failed) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + + if (priv->teamd_dbus_watch) { + g_bus_unwatch_name (priv->teamd_dbus_watch); + priv->teamd_dbus_watch = 0; + } + + if (priv->teamd_process_watch) { + g_source_remove (priv->teamd_process_watch); + priv->teamd_process_watch = 0; + } + + if (priv->teamd_pid > 0) { + service_kill (priv->teamd_pid); + priv->teamd_pid = 0; + } + +#if WITH_TEAMDCTL + if (priv->tdc) { + teamdctl_disconnect (priv->tdc); + teamdctl_free (priv->tdc); + priv->tdc = NULL; + } +#endif + + teamd_timeout_remove (dev); + + if (device_state_failed) { + if (nm_device_is_activating (dev) || + (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED)) + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + } +} + +static gboolean +teamd_timeout_cb (gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + + g_return_val_if_fail (priv->teamd_timeout, FALSE); + + nm_log_info (LOGD_TEAM, "(%s): teamd timed out.", nm_device_get_iface (dev)); + teamd_cleanup (dev, TRUE); + + return FALSE; +} + +static void +teamd_dbus_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + + g_return_if_fail (priv->teamd_dbus_watch); + + nm_log_info (LOGD_TEAM, "(%s): teamd appeared on D-Bus", nm_device_get_iface (dev)); + teamd_timeout_remove (dev); +#if WITH_TEAMDCTL + if (!ensure_teamd_connection (dev)) { + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED); + return; + } +#endif + nm_device_activate_schedule_stage2_device_config (dev); +} + +static void +teamd_dbus_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + + g_return_if_fail (priv->teamd_dbus_watch); + + if (priv->teamd_timeout) { + /* g_bus_watch_name will always raise an initial signal, to indicate whether the + * name exists/not exists initially. Do not take this as a failure, until the + * startup timeout is over. + * + * Note that g_bus_watch_name is guaranteed to alternate vanished/appeared signals, + * so we won't hit this condition again (because the next signal is either 'appeared' + * or 'timeout'). */ + nm_log_dbg (LOGD_TEAM, "(%s): teamd vanished from D-Bus (ignored)", nm_device_get_iface (dev)); + return; + } + + nm_log_info (LOGD_TEAM, "(%s): teamd vanished from D-Bus", nm_device_get_iface (dev)); + teamd_cleanup (dev, TRUE); +} + +static void +teamd_process_watch_cb (GPid pid, gint status, gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + + g_return_if_fail (priv->teamd_process_watch); + + nm_log_info (LOGD_TEAM, "(%s): teamd died", nm_device_get_iface (dev)); + priv->teamd_process_watch = 0; + priv->teamd_pid = 0; + teamd_cleanup (dev, TRUE); +} + +static void +teamd_child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process at this point. + * Give child it's own program group for signal + * separation. + */ + pid_t pid = getpid (); + setpgid (pid, pid); + + /* + * We blocked signals in main(). We need to restore original signal + * mask for avahi-autoipd here so that it can receive signals. + */ + nm_unblock_posix_signals (NULL); +} + +static gboolean +teamd_start (NMDevice *dev, NMSettingTeam *s_team) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + const char *iface = nm_device_get_ip_iface (dev); + char *tmp_str; + const char *config; + const char **teamd_binary = NULL; + static const char *teamd_paths[] = { + "/usr/bin/teamd", + "/usr/local/bin/teamd", + NULL + }; + GPtrArray *argv; + GError *error = NULL; + gboolean ret; + int status; + + if (priv->teamd_dbus_watch || + priv->teamd_process_watch || + priv->teamd_pid > 0 || +#if WITH_TEAMDCTL + priv->tdc || +#endif + priv->teamd_timeout) + { + /* Just return if teamd_start() was already called */ + return TRUE; + } + + teamd_binary = teamd_paths; + while (*teamd_binary != NULL) { + if (g_file_test (*teamd_binary, G_FILE_TEST_EXISTS)) + break; + teamd_binary++; + } + + if (!*teamd_binary) { + nm_log_warn (LOGD_TEAM, + "Activation (%s) failed to start teamd: teamd binary not found", + iface); + return FALSE; + } + + /* Kill teamd for same named device first if it is there */ + argv = g_ptr_array_new (); + g_ptr_array_add (argv, (gpointer) *teamd_binary); + g_ptr_array_add (argv, (gpointer) "-k"); + g_ptr_array_add (argv, (gpointer) "-t"); + g_ptr_array_add (argv, (gpointer) iface); + g_ptr_array_add (argv, NULL); + + tmp_str = g_strjoinv (" ", (gchar **) argv->pdata); + nm_log_dbg (LOGD_TEAM, "running: %s", tmp_str); + g_free (tmp_str); + + ret = g_spawn_sync ("/", (char **) argv->pdata, NULL, 0, nm_unblock_posix_signals, NULL, NULL, NULL, &status, &error); + g_ptr_array_free (argv, TRUE); + + /* Start teamd now */ + argv = g_ptr_array_new (); + g_ptr_array_add (argv, (gpointer) *teamd_binary); + g_ptr_array_add (argv, (gpointer) "-o"); + g_ptr_array_add (argv, (gpointer) "-n"); + g_ptr_array_add (argv, (gpointer) "-U"); + g_ptr_array_add (argv, (gpointer) "-D"); + g_ptr_array_add (argv, (gpointer) "-t"); + g_ptr_array_add (argv, (gpointer) iface); + + config = nm_setting_team_get_config(s_team); + if (config) { + g_ptr_array_add (argv, (gpointer) "-c"); + g_ptr_array_add (argv, (gpointer) config); + } + + if (nm_logging_enabled (LOGL_DEBUG, LOGD_TEAM)) + g_ptr_array_add (argv, (gpointer) "-gg"); + g_ptr_array_add (argv, NULL); + + tmp_str = g_strjoinv (" ", (gchar **) argv->pdata); + nm_log_dbg (LOGD_TEAM, "running: %s", tmp_str); + g_free (tmp_str); + + /* Start a timeout for teamd to appear at D-Bus */ + priv->teamd_timeout = g_timeout_add_seconds (5, teamd_timeout_cb, dev); + + /* Register D-Bus name watcher */ + tmp_str = g_strdup_printf ("org.libteam.teamd.%s", iface); + priv->teamd_dbus_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM, + tmp_str, + G_BUS_NAME_WATCHER_FLAGS_NONE, + teamd_dbus_appeared, + teamd_dbus_vanished, + dev, + NULL); + g_free (tmp_str); + + ret = g_spawn_async ("/", (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, + &teamd_child_setup, NULL, &priv->teamd_pid, &error); + g_ptr_array_free (argv, TRUE); + if (!ret) { + nm_log_warn (LOGD_TEAM, + "Activation (%s) failed to start teamd: %s", + iface, error->message); + g_clear_error (&error); + teamd_cleanup (dev, FALSE); + return FALSE; + } + + /* Monitor the child process so we know when it dies */ + priv->teamd_process_watch = g_child_watch_add (priv->teamd_pid, + teamd_process_watch_cb, + dev); + + nm_log_info (LOGD_TEAM, + "Activation (%s) started teamd...", iface); + return TRUE; +} + +static void +teamd_stop (NMDevice *dev) +{ + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (dev); + + if (priv->teamd_pid > 0) { + nm_log_info (LOGD_TEAM, "Deactivation (%s) stopping teamd...", + nm_device_get_ip_iface (dev)); + } else { + nm_log_dbg (LOGD_TEAM, "Deactivation (%s) stopping teamd (not started)...", + nm_device_get_ip_iface (dev)); + } + teamd_cleanup (dev, FALSE); +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; + NMConnection *connection; + NMSettingTeam *s_team; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + ret = NM_DEVICE_CLASS (nm_device_team_parent_class)->act_stage1_prepare (dev, reason); + if (ret == NM_ACT_STAGE_RETURN_SUCCESS) { + connection = nm_device_get_connection (dev); + g_assert (connection); + s_team = nm_connection_get_setting_team (connection); + g_assert (s_team); + if (teamd_start (dev, s_team)) + ret = NM_ACT_STAGE_RETURN_POSTPONE; + else + ret = NM_ACT_STAGE_RETURN_FAILURE; + } + return ret; +} + +static void +deactivate (NMDevice *dev) +{ + teamd_stop (dev); +} + +static gboolean +enslave_slave (NMDevice *device, + NMDevice *slave, + NMConnection *connection, + gboolean configure) +{ +#if WITH_TEAMDCTL + NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (device); +#endif + gboolean success = TRUE, no_firmware = FALSE; + const char *iface = nm_device_get_ip_iface (device); + const char *slave_iface = nm_device_get_ip_iface (slave); + NMSettingTeamPort *s_team_port; + + nm_device_master_check_slave_physical_port (device, slave, LOGD_TEAM); + + if (configure) { + nm_device_take_down (slave, TRUE); + + s_team_port = nm_connection_get_setting_team_port (connection); + if (s_team_port) { + const char *config = nm_setting_team_port_get_config (s_team_port); + + if (config) { +#if WITH_TEAMDCTL + if (!priv->tdc) { + nm_log_warn (LOGD_TEAM, "(%s): enslaved team port %s config not changed, not connected to teamd", + iface, slave_iface); + } else { + int err; + char *sanitized_config; + + sanitized_config = g_strdelimit (g_strdup (config), "\r\n", ' '); + err = teamdctl_port_config_update_raw (priv->tdc, slave_iface, sanitized_config); + g_free (sanitized_config); + if (err != 0) { + nm_log_err (LOGD_TEAM, "(%s): failed to update config for port %s (err=%d)", + iface, slave_iface, err); + return FALSE; + } + } +#else + nm_log_warn (LOGD_TEAM, "(%s): enslaved team port %s config not changed due to lack of Teamd control support", + iface, slave_iface); +#endif + } + } + success = nm_platform_link_enslave (nm_device_get_ip_ifindex (device), + nm_device_get_ip_ifindex (slave)); + nm_device_bring_up (slave, TRUE, &no_firmware); + + if (!success) + return FALSE; + + nm_log_info (LOGD_TEAM, "(%s): enslaved team port %s", iface, slave_iface); + } else + nm_log_info (LOGD_TEAM, "(%s): team port %s was enslaved", iface, slave_iface); + + g_object_notify (G_OBJECT (device), NM_DEVICE_TEAM_SLAVES); + + return TRUE; +} + +static gboolean +release_slave (NMDevice *device, + NMDevice *slave, + gboolean configure) +{ + gboolean success = TRUE, no_firmware = FALSE; + + if (configure) { + success = nm_platform_link_release (nm_device_get_ip_ifindex (device), + nm_device_get_ip_ifindex (slave)); + + if (success) { + nm_log_info (LOGD_TEAM, "(%s): released team port %s", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } else { + nm_log_warn (LOGD_TEAM, "(%s): failed to release team port %s", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } + } else { + nm_log_info (LOGD_TEAM, "(%s): team port %s was released", + nm_device_get_ip_iface (device), + nm_device_get_ip_iface (slave)); + } + + if (success) + g_object_notify (G_OBJECT (device), NM_DEVICE_TEAM_SLAVES); + + if (configure) { + /* Kernel team code "closes" the port when releasing it, (which clears + * IFF_UP), so we must bring it back up here to ensure carrier changes and + * other state is noticed by the now-released port. + */ + if (!nm_device_bring_up (slave, TRUE, &no_firmware)) { + nm_log_warn (LOGD_TEAM, "(%s): released team port could not be brought up.", + nm_device_get_iface (slave)); + } + } + + return success; +} + +/******************************************************************/ + +NMDevice * +nm_device_team_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_TEAM, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_DRIVER, "team", + NM_DEVICE_TYPE_DESC, "Team", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_TEAM, + NM_DEVICE_IS_MASTER, TRUE, + NULL); +} + +NMDevice * +nm_device_team_new_for_connection (NMConnection *connection) +{ + const char *iface; + + g_return_val_if_fail (connection != NULL, NULL); + + iface = nm_connection_get_virtual_iface_name (connection); + g_return_val_if_fail (iface != NULL, NULL); + + if ( !nm_platform_team_add (iface) + && nm_platform_get_error () != NM_PLATFORM_ERROR_EXISTS) { + nm_log_warn (LOGD_DEVICE | LOGD_TEAM, "(%s): failed to create team master interface for '%s': %s", + iface, nm_connection_get_id (connection), + nm_platform_get_error_msg ()); + return NULL; + } + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_TEAM, + NM_DEVICE_IFACE, iface, + NM_DEVICE_DRIVER, "team", + NM_DEVICE_TYPE_DESC, "Team", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_TEAM, + NM_DEVICE_IS_MASTER, TRUE, + NULL); +} + +static void +constructed (GObject *object) +{ + G_OBJECT_CLASS (nm_device_team_parent_class)->constructed (object); + + nm_log_dbg (LOGD_HW | LOGD_TEAM, "(%s): kernel ifindex %d", + nm_device_get_iface (NM_DEVICE (object)), + nm_device_get_ifindex (NM_DEVICE (object))); +} + +static void +nm_device_team_init (NMDeviceTeam * self) +{ +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GPtrArray *slaves; + GSList *list, *iter; + + switch (prop_id) { + break; + case PROP_SLAVES: + slaves = g_ptr_array_new (); + list = nm_device_master_get_slaves (NM_DEVICE (object)); + for (iter = list; iter; iter = iter->next) + g_ptr_array_add (slaves, g_strdup (nm_device_get_path (NM_DEVICE (iter->data)))); + g_slist_free (list); + g_value_take_boxed (value, slaves); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + teamd_cleanup (NM_DEVICE (object), FALSE); + + G_OBJECT_CLASS (nm_device_team_parent_class)->dispose (object); +} + +static void +nm_device_team_class_init (NMDeviceTeamClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceTeamPrivate)); + + parent_class->connection_type = NM_SETTING_TEAM_SETTING_NAME; + + /* virtual methods */ + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + parent_class->get_generic_capabilities = get_generic_capabilities; + parent_class->is_available = is_available; + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->check_connection_available = check_connection_available; + parent_class->complete_connection = complete_connection; + parent_class->update_connection = update_connection; + + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->deactivate = deactivate; + parent_class->enslave_slave = enslave_slave; + parent_class->release_slave = release_slave; + + /* properties */ + g_object_class_install_property + (object_class, PROP_SLAVES, + g_param_spec_boxed (NM_DEVICE_TEAM_SLAVES, + "Slaves", + "Slaves", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_team_object_info); + + dbus_g_error_domain_register (NM_TEAM_ERROR, NULL, NM_TYPE_TEAM_ERROR); +} diff --git a/src/devices/nm-device-team.h b/src/devices/nm-device-team.h new file mode 100644 index 000000000..fe1275c6b --- /dev/null +++ b/src/devices/nm-device-team.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef NM_DEVICE_TEAM_H +#define NM_DEVICE_TEAM_H + +#include <glib-object.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_TEAM (nm_device_team_get_type ()) +#define NM_DEVICE_TEAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_TEAM, NMDeviceTeam)) +#define NM_DEVICE_TEAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_TEAM, NMDeviceTeamClass)) +#define NM_IS_DEVICE_TEAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_TEAM)) +#define NM_IS_DEVICE_TEAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_TEAM)) +#define NM_DEVICE_TEAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_TEAM, NMDeviceTeamClass)) + +typedef enum { + NM_TEAM_ERROR_CONNECTION_NOT_TEAM = 0, /*< nick=ConnectionNotTeam >*/ + NM_TEAM_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_TEAM_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMTeamError; + +#define NM_DEVICE_TEAM_SLAVES "slaves" + +typedef struct { + NMDevice parent; +} NMDeviceTeam; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceTeamClass; + + +GType nm_device_team_get_type (void); + +NMDevice *nm_device_team_new (NMPlatformLink *platform_device); +NMDevice *nm_device_team_new_for_connection (NMConnection *connection); + +gboolean nm_team_update_slave_connection (NMDevice *slave, NMConnection *connection); + +G_END_DECLS + +#endif /* NM_DEVICE_TEAM_H */ diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c new file mode 100644 index 000000000..d52228f99 --- /dev/null +++ b/src/devices/nm-device-tun.c @@ -0,0 +1,294 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> + +#include "nm-device-tun.h" +#include "nm-device-private.h" +#include "nm-dbus-manager.h" +#include "nm-logging.h" +#include "nm-platform.h" + +#include "nm-device-tun-glue.h" + +G_DEFINE_TYPE (NMDeviceTun, nm_device_tun, NM_TYPE_DEVICE_GENERIC) + +#define NM_DEVICE_TUN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_TUN, NMDeviceTunPrivate)) + +typedef struct { + NMPlatformTunProperties props; + const char *mode; + guint delay_tun_get_properties_id; +} NMDeviceTunPrivate; + +enum { + PROP_0, + PROP_OWNER, + PROP_GROUP, + PROP_FLAGS, + PROP_MODE, + PROP_NO_PI, + PROP_VNET_HDR, + PROP_MULTI_QUEUE, + + LAST_PROP +}; + +static void +reload_tun_properties (NMDeviceTun *device) +{ + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (device); + GObject *object = G_OBJECT (device); + NMPlatformTunProperties props; + + if (!nm_platform_tun_get_properties (nm_device_get_ifindex (NM_DEVICE (device)), &props)) { + nm_log_warn (LOGD_HW, "(%s): could not read tun properties", + nm_device_get_iface (NM_DEVICE (device))); + return; + } + + g_object_freeze_notify (object); + + if (priv->props.owner != props.owner) + g_object_notify (object, NM_DEVICE_TUN_OWNER); + if (priv->props.group != props.group) + g_object_notify (object, NM_DEVICE_TUN_GROUP); + if (priv->props.no_pi != props.no_pi) + g_object_notify (object, NM_DEVICE_TUN_NO_PI); + if (priv->props.vnet_hdr != props.vnet_hdr) + g_object_notify (object, NM_DEVICE_TUN_VNET_HDR); + if (priv->props.multi_queue != props.multi_queue) + g_object_notify (object, NM_DEVICE_TUN_MULTI_QUEUE); + + memcpy (&priv->props, &props, sizeof (NMPlatformTunProperties)); + + g_object_thaw_notify (object); +} + +static void +link_changed (NMDevice *device, NMPlatformLink *info) +{ + NM_DEVICE_CLASS (nm_device_tun_parent_class)->link_changed (device, info); + + reload_tun_properties (NM_DEVICE_TUN (device)); +} + +static gboolean +delay_tun_get_properties_cb (gpointer user_data) +{ + NMDeviceTun *self = user_data; + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self); + + priv->delay_tun_get_properties_id = 0; + + reload_tun_properties (self); + + return G_SOURCE_REMOVE; +} + +/**************************************************************/ + +NMDevice * +nm_device_tun_new (NMPlatformLink *platform_device) +{ + const char *mode = NULL; + + g_return_val_if_fail (platform_device != NULL, NULL); + + if (platform_device->type == NM_LINK_TYPE_TUN) + mode = "tun"; + else if (platform_device->type == NM_LINK_TYPE_TAP) + mode = "tap"; + g_return_val_if_fail (mode != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_TUN, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "Tun", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_GENERIC, + NM_DEVICE_TUN_MODE, mode, + NULL); +} + +static void +nm_device_tun_init (NMDeviceTun *self) +{ +} + +static void +constructed (GObject *object) +{ + gboolean properties_read; + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (object); + + properties_read = nm_platform_tun_get_properties (nm_device_get_ifindex (NM_DEVICE (object)), &priv->props); + + G_OBJECT_CLASS (nm_device_tun_parent_class)->constructed (object); + + if (!properties_read) { + /* Error reading the tun properties. Maybe this was due to a race. Try again a bit later. */ + nm_log_dbg (LOGD_HW, "(%s): could not read tun properties (retry)", + nm_device_get_iface (NM_DEVICE (object))); + priv->delay_tun_get_properties_id = g_timeout_add_seconds (1, delay_tun_get_properties_cb, object); + } +} + +static void +dispose (GObject *object) +{ + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (object); + + if (priv->delay_tun_get_properties_id) { + g_source_remove (priv->delay_tun_get_properties_id); + priv->delay_tun_get_properties_id = 0; + } + + G_OBJECT_CLASS (nm_device_tun_parent_class)->dispose (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceTun *self = NM_DEVICE_TUN (object); + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self); + + switch (prop_id) { + case PROP_OWNER: + g_value_set_uint (value, priv->props.owner); + break; + case PROP_GROUP: + g_value_set_uint (value, priv->props.group); + break; + case PROP_MODE: + g_value_set_string (value, priv->mode); + break; + case PROP_NO_PI: + g_value_set_boolean (value, priv->props.no_pi); + break; + case PROP_VNET_HDR: + g_value_set_boolean (value, priv->props.vnet_hdr); + break; + case PROP_MULTI_QUEUE: + g_value_set_boolean (value, priv->props.multi_queue); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMDeviceTun *self = NM_DEVICE_TUN (object); + NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self); + const char *str; + + switch (prop_id) { + case PROP_MODE: + /* construct-only */ + str = g_value_get_string (value); + + /* mode is G_PARAM_STATIC_STRINGS */ + if (g_strcmp0 (str, "tun") == 0) + priv->mode = "tun"; + else if (g_strcmp0 (str, "tap") == 0) + priv->mode = "tap"; + else + g_return_if_fail (FALSE); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_tun_class_init (NMDeviceTunClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMDeviceTunPrivate)); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + device_class->link_changed = link_changed; + + /* properties */ + g_object_class_install_property + (object_class, PROP_OWNER, + g_param_spec_int64 (NM_DEVICE_TUN_OWNER, + "Owner", + "Owner", + -1, G_MAXUINT32, -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_GROUP, + g_param_spec_int64 (NM_DEVICE_TUN_GROUP, + "Group", + "Group", + -1, G_MAXUINT32, -1, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_MODE, + g_param_spec_string (NM_DEVICE_TUN_MODE, + "Mode", + "Mode", + "tun", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_NO_PI, + g_param_spec_boolean (NM_DEVICE_TUN_NO_PI, + "No Protocol Info", + "No Protocol Info", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_VNET_HDR, + g_param_spec_boolean (NM_DEVICE_TUN_VNET_HDR, + "Virtio networking header", + "Virtio networking header", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_MULTI_QUEUE, + g_param_spec_boolean (NM_DEVICE_TUN_MULTI_QUEUE, + "Multi-queue", + "Multi-queue", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_tun_object_info); +} diff --git a/src/devices/nm-device-tun.h b/src/devices/nm-device-tun.h new file mode 100644 index 000000000..cfcf4d7be --- /dev/null +++ b/src/devices/nm-device-tun.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_TUN_H +#define NM_DEVICE_TUN_H + +#include <glib-object.h> + +#include "nm-device-generic.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_TUN (nm_device_tun_get_type ()) +#define NM_DEVICE_TUN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_TUN, NMDeviceTun)) +#define NM_DEVICE_TUN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_TUN, NMDeviceTunClass)) +#define NM_IS_DEVICE_TUN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_TUN)) +#define NM_IS_DEVICE_TUN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_TUN)) +#define NM_DEVICE_TUN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_TUN, NMDeviceTunClass)) + +#define NM_DEVICE_TUN_OWNER "owner" +#define NM_DEVICE_TUN_GROUP "group" +#define NM_DEVICE_TUN_MODE "mode" +#define NM_DEVICE_TUN_NO_PI "no-pi" +#define NM_DEVICE_TUN_VNET_HDR "vnet-hdr" +#define NM_DEVICE_TUN_MULTI_QUEUE "multi-queue" + +typedef struct { + NMDeviceGeneric parent; +} NMDeviceTun; + +typedef struct { + NMDeviceGenericClass parent; + +} NMDeviceTunClass; + +GType nm_device_tun_get_type (void); + +NMDevice *nm_device_tun_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_TUN_H */ diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c new file mode 100644 index 000000000..df1075238 --- /dev/null +++ b/src/devices/nm-device-veth.c @@ -0,0 +1,171 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#include "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/sockios.h> +#include <sys/ioctl.h> + +#include "nm-device-veth.h" +#include "nm-device-private.h" +#include "nm-logging.h" +#include "nm-manager.h" +#include "nm-platform.h" +#include "nm-dbus-manager.h" + +#include "nm-device-veth-glue.h" + +G_DEFINE_TYPE (NMDeviceVeth, nm_device_veth, NM_TYPE_DEVICE_ETHERNET) + +#define NM_DEVICE_VETH_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_VETH, NMDeviceVethPrivate)) + +typedef struct { + NMDevice *peer; + gboolean ever_had_peer; +} NMDeviceVethPrivate; + +enum { + PROP_0, + PROP_PEER, + + LAST_PROP +}; + +/**************************************************************/ + +static void +set_peer (NMDeviceVeth *self, NMDevice *peer) +{ + NMDeviceVethPrivate *priv = NM_DEVICE_VETH_GET_PRIVATE (self); + + if (!priv->peer) { + priv->ever_had_peer = TRUE; + priv->peer = peer; + g_object_add_weak_pointer (G_OBJECT (peer), (gpointer *) &priv->peer); + + g_object_notify (G_OBJECT (self), NM_DEVICE_VETH_PEER); + } +} + +static NMDevice * +get_peer (NMDeviceVeth *self) +{ + NMDeviceVethPrivate *priv = NM_DEVICE_VETH_GET_PRIVATE (self); + NMDevice *device = NM_DEVICE (self), *peer = NULL; + NMPlatformVethProperties props; + + if (priv->ever_had_peer) + return priv->peer; + + if (!nm_platform_veth_get_properties (nm_device_get_ifindex (device), &props)) { + nm_log_warn (LOGD_HW, "(%s): could not read veth properties", + nm_device_get_iface (device)); + return NULL; + } + + peer = nm_manager_get_device_by_ifindex (nm_manager_get (), props.peer); + if (peer && NM_IS_DEVICE_VETH (peer)) { + set_peer (self, peer); + set_peer (NM_DEVICE_VETH (peer), device); + } + + return priv->peer; +} + + +/**************************************************************/ + +NMDevice * +nm_device_veth_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_VETH, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "Veth", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_ETHERNET, + NULL); +} + +static void +nm_device_veth_init (NMDeviceVeth *self) +{ + nm_device_set_initial_unmanaged_flag (NM_DEVICE (self), NM_UNMANAGED_DEFAULT, TRUE); +} + +static void +dispose (GObject *object) +{ + NMDeviceVeth *self = NM_DEVICE_VETH (object); + NMDeviceVethPrivate *priv = NM_DEVICE_VETH_GET_PRIVATE (self); + + if (priv->peer) { + g_object_remove_weak_pointer (G_OBJECT (priv->peer), (gpointer *) &priv->peer); + priv->peer = NULL; + } + + G_OBJECT_CLASS (nm_device_veth_parent_class)->dispose (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceVeth *self = NM_DEVICE_VETH (object); + NMDevice *peer; + + switch (prop_id) { + case PROP_PEER: + peer = get_peer (self); + g_value_set_boxed (value, peer ? nm_device_get_path (peer) : "/"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_veth_class_init (NMDeviceVethClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMDeviceVethPrivate)); + + object_class->get_property = get_property; + object_class->dispose = dispose; + + /* properties */ + g_object_class_install_property + (object_class, PROP_PEER, + g_param_spec_boxed (NM_DEVICE_VETH_PEER, + "Peer", + "Peer device", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_veth_object_info); +} diff --git a/src/devices/nm-device-veth.h b/src/devices/nm-device-veth.h new file mode 100644 index 000000000..bc8fedffd --- /dev/null +++ b/src/devices/nm-device-veth.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_VETH_H +#define NM_DEVICE_VETH_H + +#include <glib-object.h> + +#include "nm-device-ethernet.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_VETH (nm_device_veth_get_type ()) +#define NM_DEVICE_VETH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_VETH, NMDeviceVeth)) +#define NM_DEVICE_VETH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_VETH, NMDeviceVethClass)) +#define NM_IS_DEVICE_VETH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_VETH)) +#define NM_IS_DEVICE_VETH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_VETH)) +#define NM_DEVICE_VETH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_VETH, NMDeviceVethClass)) + +#define NM_DEVICE_VETH_PEER "peer" + +typedef struct { + NMDeviceEthernet parent; +} NMDeviceVeth; + +typedef struct { + NMDeviceEthernetClass parent; + +} NMDeviceVethClass; + +GType nm_device_veth_get_type (void); + +NMDevice *nm_device_veth_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_VETH_H */ diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c new file mode 100644 index 000000000..348be3528 --- /dev/null +++ b/src/devices/nm-device-vlan.c @@ -0,0 +1,659 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2011 - 2012 Red Hat, Inc. + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> + +#include <sys/socket.h> +#include <linux/if.h> +#include <netinet/ether.h> + +#include "nm-device-vlan.h" +#include "nm-manager.h" +#include "nm-logging.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-enum-types.h" +#include "nm-dbus-manager.h" +#include "nm-platform.h" +#include "nm-utils.h" + +#include "nm-device-vlan-glue.h" + + +G_DEFINE_TYPE (NMDeviceVlan, nm_device_vlan, NM_TYPE_DEVICE) + +#define NM_DEVICE_VLAN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_VLAN, NMDeviceVlanPrivate)) + +#define NM_VLAN_ERROR (nm_vlan_error_quark ()) + +typedef struct { + guint8 initial_hw_addr[ETH_ALEN]; + + gboolean disposed; + gboolean invalid; + + NMDevice *parent; + guint parent_state_id; + + int vlan_id; +} NMDeviceVlanPrivate; + +enum { + PROP_0, + PROP_PARENT, + PROP_VLAN_ID, + + LAST_PROP +}; + +/******************************************************************/ + +static GQuark +nm_vlan_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-vlan-error"); + return quark; +} + +/******************************************************************/ + +static void +update_initial_hw_address (NMDevice *dev) +{ + NMDeviceVlan *self = NM_DEVICE_VLAN (dev); + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (self); + char *mac_str; + + memcpy (priv->initial_hw_addr, nm_device_get_hw_address (dev, NULL), ETH_ALEN); + + mac_str = nm_utils_hwaddr_ntoa (priv->initial_hw_addr, ARPHRD_ETHER); + nm_log_dbg (LOGD_DEVICE | LOGD_VLAN, "(%s): read initial MAC address %s", + nm_device_get_iface (dev), mac_str); + g_free (mac_str); +} + +static guint32 +get_generic_capabilities (NMDevice *dev) +{ + /* We assume VLAN interfaces always support carrier detect */ + return NM_DEVICE_CAP_CARRIER_DETECT; +} + +static gboolean +bring_up (NMDevice *dev, gboolean *no_firmware) +{ + gboolean success = FALSE; + guint i = 20; + + while (i-- > 0 && !success) { + success = NM_DEVICE_CLASS (nm_device_vlan_parent_class)->bring_up (dev, no_firmware); + g_usleep (50); + } + + return success; +} + +/******************************************************************/ + +static gboolean +match_parent (NMDeviceVlan *self, const char *parent) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (self); + + g_return_val_if_fail (parent != NULL, FALSE); + + if (nm_utils_is_uuid (parent)) { + NMActRequest *parent_req; + NMConnection *parent_connection; + + /* If the parent is a UUID, the connection matches if our parent + * device has that connection activated. + */ + + parent_req = nm_device_get_act_request (priv->parent); + if (!parent_req) + return FALSE; + + parent_connection = nm_active_connection_get_connection (NM_ACTIVE_CONNECTION (parent_req)); + if (!parent_connection) + return FALSE; + + if (g_strcmp0 (parent, nm_connection_get_uuid (parent_connection)) != 0) + return FALSE; + } else { + /* interface name */ + if (g_strcmp0 (parent, nm_device_get_ip_iface (priv->parent)) != 0) + return FALSE; + } + + return TRUE; +} + +static gboolean +match_hwaddr (NMDevice *device, NMConnection *connection, gboolean fail_if_no_hwaddr) +{ + NMSettingWired *s_wired; + const GByteArray *mac; + const guint8 *device_mac; + guint device_mac_len; + + s_wired = nm_connection_get_setting_wired (connection); + if (!s_wired) + return !fail_if_no_hwaddr; + + mac = nm_setting_wired_get_mac_address (s_wired); + if (!mac) + return !fail_if_no_hwaddr; + + device_mac = nm_device_get_hw_address (device, &device_mac_len); + + return ( mac->len == device_mac_len + && memcmp (mac->data, device_mac, device_mac_len) == 0); +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (device); + NMSettingVlan *s_vlan; + const char *parent, *iface = NULL; + + if (!NM_DEVICE_CLASS (nm_device_vlan_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_vlan = nm_connection_get_setting_vlan (connection); + if (!s_vlan) + return FALSE; + + if (nm_setting_vlan_get_id (s_vlan) != priv->vlan_id) + return FALSE; + + /* Check parent interface; could be an interface name or a UUID */ + parent = nm_setting_vlan_get_parent (s_vlan); + if (parent) { + if (!match_parent (NM_DEVICE_VLAN (device), parent)) + return FALSE; + } else { + /* Parent could be a MAC address in an NMSettingWired */ + if (!match_hwaddr (device, connection, TRUE)) + return FALSE; + } + + /* Ensure the interface name matches. If not specified we assume a match + * since both the parent interface and the VLAN ID matched by the time we + * get here. + */ + iface = nm_connection_get_virtual_iface_name (connection); + if (iface) { + if (g_strcmp0 (nm_device_get_ip_iface (device), iface) != 0) + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMSettingVlan *s_vlan; + + nm_utils_complete_generic (connection, + NM_SETTING_VLAN_SETTING_NAME, + existing_connections, + _("VLAN connection %d"), + NULL, + TRUE); + + s_vlan = nm_connection_get_setting_vlan (connection); + if (!s_vlan) { + g_set_error_literal (error, NM_VLAN_ERROR, NM_VLAN_ERROR_CONNECTION_INVALID, + "A 'vlan' setting is required."); + return FALSE; + } + + /* If there's no VLAN interface, no parent, and no hardware address in the + * settings, then there's not enough information to complete the setting. + */ + if ( !nm_setting_vlan_get_parent (s_vlan) + && !match_hwaddr (device, connection, TRUE)) { + g_set_error_literal (error, NM_VLAN_ERROR, NM_VLAN_ERROR_CONNECTION_INVALID, + "The 'vlan' setting had no interface name, parent, or hardware address."); + return FALSE; + } + + return TRUE; +} + +static void parent_state_changed (NMDevice *parent, NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data); + +static void +nm_device_vlan_set_parent (NMDeviceVlan *device, NMDevice *parent) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (device); + + if (priv->parent_state_id) { + g_signal_handler_disconnect (priv->parent, priv->parent_state_id); + priv->parent_state_id = 0; + } + g_clear_object (&priv->parent); + + if (parent) { + priv->parent = g_object_ref (parent); + priv->parent_state_id = g_signal_connect (priv->parent, + "state-changed", + G_CALLBACK (parent_state_changed), + device); + } + g_object_notify (G_OBJECT (device), NM_DEVICE_VLAN_PARENT); +} + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (device); + NMSettingVlan *s_vlan = nm_connection_get_setting_vlan (connection); + int ifindex = nm_device_get_ifindex (device); + int parent_ifindex = -1, vlan_id = -1; + NMDevice *parent; + const char *setting_parent, *new_parent; + + if (!s_vlan) { + s_vlan = (NMSettingVlan *) nm_setting_vlan_new (); + nm_connection_add_setting (connection, (NMSetting *) s_vlan); + g_object_set (s_vlan, NM_SETTING_VLAN_INTERFACE_NAME, nm_device_get_iface (device), NULL); + } + + if (!nm_platform_vlan_get_info (ifindex, &parent_ifindex, &vlan_id)) { + nm_log_warn (LOGD_VLAN, "(%s): failed to get VLAN interface info while updating connection.", + nm_device_get_iface (device)); + return; + } + + if (priv->vlan_id != vlan_id) { + priv->vlan_id = vlan_id; + g_object_notify (G_OBJECT (device), NM_DEVICE_VLAN_ID); + } + + if (vlan_id != nm_setting_vlan_get_id (s_vlan)) + g_object_set (s_vlan, NM_SETTING_VLAN_ID, priv->vlan_id, NULL); + + parent = nm_manager_get_device_by_ifindex (nm_manager_get (), parent_ifindex); + g_assert (parent); + if (priv->parent != parent) + nm_device_vlan_set_parent (NM_DEVICE_VLAN (device), parent); + + /* Update parent in the connection; default to parent's interface name */ + new_parent = nm_device_get_iface (parent); + setting_parent = nm_setting_vlan_get_parent (s_vlan); + if (setting_parent && nm_utils_is_uuid (setting_parent)) { + NMConnection *parent_connection; + + /* Don't change a parent specified by UUID if it's still valid */ + parent_connection = nm_connection_provider_get_connection_by_uuid (nm_connection_provider_get (), setting_parent); + if (parent_connection && nm_device_check_connection_compatible (parent, parent_connection)) + new_parent = NULL; + } + if (new_parent) + g_object_set (s_vlan, NM_SETTING_VLAN_PARENT, new_parent, NULL); +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMActRequest *req; + NMConnection *connection; + NMSettingVlan *s_vlan; + NMSettingWired *s_wired; + const GByteArray *cloned_mac; + NMActStageReturn ret; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + ret = NM_DEVICE_CLASS (nm_device_vlan_parent_class)->act_stage1_prepare (dev, reason); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) + return ret; + + req = nm_device_get_act_request (dev); + g_return_val_if_fail (req != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_connection (req); + g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + s_wired = nm_connection_get_setting_wired (connection); + if (s_wired) { + /* Set device MAC address if the connection wants to change it */ + cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired); + if (cloned_mac && (cloned_mac->len == ETH_ALEN)) + nm_device_set_hw_addr (dev, (const guint8 *) cloned_mac->data, "set", LOGD_VLAN); + } + + s_vlan = nm_connection_get_setting_vlan (connection); + if (s_vlan) { + int ifindex = nm_device_get_ifindex (dev); + int num, i; + guint32 from, to; + + num = nm_setting_vlan_get_num_priorities (s_vlan, NM_VLAN_INGRESS_MAP); + for (i = 0; i < num; i++) { + if (nm_setting_vlan_get_priority (s_vlan, NM_VLAN_INGRESS_MAP, i, &from, &to)) + nm_platform_vlan_set_ingress_map (ifindex, from, to); + } + num = nm_setting_vlan_get_num_priorities (s_vlan, NM_VLAN_EGRESS_MAP); + for (i = 0; i < num; i++) { + if (nm_setting_vlan_get_priority (s_vlan, NM_VLAN_EGRESS_MAP, i, &from, &to)) + nm_platform_vlan_set_egress_map (ifindex, from, to); + } + } + + return ret; +} + +static void +ip4_config_pre_commit (NMDevice *device, NMIP4Config *config) +{ + NMConnection *connection; + NMSettingWired *s_wired; + guint32 mtu; + + connection = nm_device_get_connection (device); + g_assert (connection); + + s_wired = nm_connection_get_setting_wired (connection); + if (s_wired) { + mtu = nm_setting_wired_get_mtu (s_wired); + if (mtu) + nm_ip4_config_set_mtu (config, mtu); + } +} + +static void +deactivate (NMDevice *device) +{ + NMDeviceVlan *self = NM_DEVICE_VLAN (device); + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (self); + + /* Reset MAC address back to initial address */ + nm_device_set_hw_addr (device, priv->initial_hw_addr, "reset", LOGD_VLAN); +} + +/******************************************************************/ + +static void +parent_state_changed (NMDevice *parent, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDeviceVlan *self = NM_DEVICE_VLAN (user_data); + + /* We'll react to our own carrier state notifications. Ignore the parent's. */ + if (reason == NM_DEVICE_STATE_REASON_CARRIER) + return; + + if (new_state < NM_DEVICE_STATE_DISCONNECTED) { + /* If the parent becomes unavailable or unmanaged so does the VLAN */ + nm_device_state_changed (NM_DEVICE (self), new_state, reason); + } else if ( new_state == NM_DEVICE_STATE_DISCONNECTED + && old_state < NM_DEVICE_STATE_DISCONNECTED) { + /* Mark VLAN interface as available/disconnected when the parent + * becomes available as a result of becoming initialized. + */ + nm_device_state_changed (NM_DEVICE (self), new_state, reason); + } +} + +/******************************************************************/ + +NMDevice * +nm_device_vlan_new (NMPlatformLink *platform_device, NMDevice *parent) +{ + NMDevice *device; + + g_return_val_if_fail (platform_device != NULL, NULL); + g_return_val_if_fail (NM_IS_DEVICE (parent), NULL); + + device = (NMDevice *) g_object_new (NM_TYPE_DEVICE_VLAN, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_VLAN_PARENT, parent, + NM_DEVICE_DRIVER, "8021q", + NM_DEVICE_TYPE_DESC, "VLAN", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_VLAN, + NULL); + if (NM_DEVICE_VLAN_GET_PRIVATE (device)->invalid) { + g_object_unref (device); + device = NULL; + } + + return device; +} + +NMDevice * +nm_device_vlan_new_for_connection (NMConnection *connection, NMDevice *parent) +{ + NMDevice *device; + NMSettingVlan *s_vlan; + char *iface; + + g_return_val_if_fail (connection != NULL, NULL); + g_return_val_if_fail (NM_IS_DEVICE (parent), NULL); + + s_vlan = nm_connection_get_setting_vlan (connection); + g_return_val_if_fail (s_vlan != NULL, NULL); + + iface = g_strdup (nm_connection_get_virtual_iface_name (connection)); + if (!iface) { + iface = nm_utils_new_vlan_name (nm_device_get_ip_iface (parent), + nm_setting_vlan_get_id (s_vlan)); + } + + if ( !nm_platform_vlan_add (iface, + nm_device_get_ifindex (parent), + nm_setting_vlan_get_id (s_vlan), + nm_setting_vlan_get_flags (s_vlan)) + && nm_platform_get_error () != NM_PLATFORM_ERROR_EXISTS) { + nm_log_warn (LOGD_DEVICE | LOGD_VLAN, "(%s): failed to add VLAN interface for '%s'", + iface, nm_connection_get_id (connection)); + g_free (iface); + return NULL; + } + + device = (NMDevice *) g_object_new (NM_TYPE_DEVICE_VLAN, + NM_DEVICE_IFACE, iface, + NM_DEVICE_VLAN_PARENT, parent, + NM_DEVICE_DRIVER, "8021q", + NM_DEVICE_TYPE_DESC, "VLAN", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_VLAN, + NULL); + g_free (iface); + if (NM_DEVICE_VLAN_GET_PRIVATE (device)->invalid) { + g_object_unref (device); + device = NULL; + } + + return device; +} + +static void +nm_device_vlan_init (NMDeviceVlan * self) +{ +} + +static void +constructed (GObject *object) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (object); + NMDevice *device = NM_DEVICE (object); + const char *iface = nm_device_get_iface (device); + int ifindex = nm_device_get_ifindex (device); + int parent_ifindex = -1, itype; + int vlan_id; + + if (G_OBJECT_CLASS (nm_device_vlan_parent_class)->constructed) + G_OBJECT_CLASS (nm_device_vlan_parent_class)->constructed (object); + + if (!priv->parent) { + nm_log_err (LOGD_VLAN, "(%s): no parent specified.", iface); + priv->invalid = TRUE; + return; + } + + itype = nm_platform_link_get_type (ifindex); + if (itype != NM_LINK_TYPE_VLAN) { + nm_log_err (LOGD_VLAN, "(%s): failed to get VLAN interface type.", iface); + priv->invalid = TRUE; + return; + } + + if (!nm_platform_vlan_get_info (ifindex, &parent_ifindex, &vlan_id)) { + nm_log_warn (LOGD_VLAN, "(%s): failed to get VLAN interface info.", iface); + priv->invalid = TRUE; + return; + } + + if ( parent_ifindex < 0 + || parent_ifindex != nm_device_get_ip_ifindex (priv->parent) + || vlan_id < 0) { + nm_log_warn (LOGD_VLAN, "(%s): VLAN parent ifindex (%d) or VLAN ID (%d) invalid.", + iface, parent_ifindex, priv->vlan_id); + priv->invalid = TRUE; + return; + } + + priv->vlan_id = vlan_id; + nm_log_dbg (LOGD_HW | LOGD_VLAN, "(%s): kernel ifindex %d", iface, ifindex); + nm_log_info (LOGD_HW | LOGD_VLAN, "(%s): VLAN ID %d with parent %s", + iface, priv->vlan_id, nm_device_get_iface (priv->parent)); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_VLAN_ID: + g_value_set_uint (value, priv->vlan_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_PARENT: + nm_device_vlan_set_parent (NM_DEVICE_VLAN (object), g_value_get_object (value)); + break; + case PROP_VLAN_ID: + priv->vlan_id = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMDeviceVlan *self = NM_DEVICE_VLAN (object); + NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (self); + + if (priv->disposed) { + G_OBJECT_CLASS (nm_device_vlan_parent_class)->dispose (object); + return; + } + priv->disposed = TRUE; + + nm_device_vlan_set_parent (self, NULL); + + G_OBJECT_CLASS (nm_device_vlan_parent_class)->dispose (object); +} + +static void +nm_device_vlan_class_init (NMDeviceVlanClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + parent_class->connection_type = NM_SETTING_VLAN_SETTING_NAME; + + g_type_class_add_private (object_class, sizeof (NMDeviceVlanPrivate)); + + /* virtual methods */ + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + parent_class->update_initial_hw_address = update_initial_hw_address; + parent_class->get_generic_capabilities = get_generic_capabilities; + parent_class->bring_up = bring_up; + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->ip4_config_pre_commit = ip4_config_pre_commit; + parent_class->deactivate = deactivate; + + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->complete_connection = complete_connection; + parent_class->update_connection = update_connection; + + /* properties */ + g_object_class_install_property + (object_class, PROP_PARENT, + g_param_spec_object (NM_DEVICE_VLAN_PARENT, + "Parent", + "Parent", + NM_TYPE_DEVICE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property + (object_class, PROP_VLAN_ID, + g_param_spec_uint (NM_DEVICE_VLAN_ID, + "VLAN ID", + "VLAN ID", + 0, 4095, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_vlan_object_info); + + dbus_g_error_domain_register (NM_VLAN_ERROR, NULL, NM_TYPE_VLAN_ERROR); +} diff --git a/src/devices/nm-device-vlan.h b/src/devices/nm-device-vlan.h new file mode 100644 index 000000000..0f2ac70d5 --- /dev/null +++ b/src/devices/nm-device-vlan.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2012 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_VLAN_H +#define NM_DEVICE_VLAN_H + +#include <glib-object.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_VLAN (nm_device_vlan_get_type ()) +#define NM_DEVICE_VLAN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_VLAN, NMDeviceVlan)) +#define NM_DEVICE_VLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_VLAN, NMDeviceVlanClass)) +#define NM_IS_DEVICE_VLAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_VLAN)) +#define NM_IS_DEVICE_VLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_VLAN)) +#define NM_DEVICE_VLAN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_VLAN, NMDeviceVlanClass)) + +typedef enum { + NM_VLAN_ERROR_CONNECTION_NOT_VLAN = 0, /*< nick=ConnectionNotVlan >*/ + NM_VLAN_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_VLAN_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMVlanError; + +#define NM_DEVICE_VLAN_PARENT "parent" +#define NM_DEVICE_VLAN_ID "vlan-id" + +typedef struct { + NMDevice parent; +} NMDeviceVlan; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceVlanClass; + + +GType nm_device_vlan_get_type (void); + +NMDevice *nm_device_vlan_new (NMPlatformLink *platform_link, + NMDevice *parent); +NMDevice *nm_device_vlan_new_for_connection (NMConnection *connection, + NMDevice *parent); + +G_END_DECLS + +#endif /* NM_DEVICE_VLAN_H */ diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c new file mode 100644 index 000000000..0be57106e --- /dev/null +++ b/src/devices/nm-device-vxlan.c @@ -0,0 +1,372 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013, 2014 Red Hat, Inc. + */ + +#include "config.h" + +#include <string.h> + +#include "nm-device-vxlan.h" +#include "nm-device-private.h" +#include "nm-dbus-manager.h" +#include "nm-logging.h" +#include "nm-manager.h" +#include "nm-platform.h" +#include "nm-utils.h" + +#include "nm-device-vxlan-glue.h" + +G_DEFINE_TYPE (NMDeviceVxlan, nm_device_vxlan, NM_TYPE_DEVICE_GENERIC) + +#define NM_DEVICE_VXLAN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlanPrivate)) + +typedef struct { + NMPlatformVxlanProperties props; +} NMDeviceVxlanPrivate; + +enum { + PROP_0, + PROP_PARENT, + PROP_ID, + PROP_GROUP, + PROP_LOCAL, + PROP_TOS, + PROP_TTL, + PROP_LEARNING, + PROP_AGEING, + PROP_LIMIT, + PROP_DST_PORT, + PROP_SRC_PORT_MIN, + PROP_SRC_PORT_MAX, + PROP_PROXY, + PROP_RSC, + PROP_L2MISS, + PROP_L3MISS, + + LAST_PROP +}; + +/**************************************************************/ + +static void +update_properties (NMDevice *device) +{ + NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE (device); + GObject *object = G_OBJECT (device); + NMPlatformVxlanProperties props; + + if (!nm_platform_vxlan_get_properties (nm_device_get_ifindex (device), &props)) { + nm_log_warn (LOGD_HW, "(%s): could not read vxlan properties", + nm_device_get_iface (device)); + return; + } + + g_object_freeze_notify (object); + + if (priv->props.parent_ifindex != props.parent_ifindex) + g_object_notify (object, NM_DEVICE_VXLAN_PARENT); + if (priv->props.id != props.id) + g_object_notify (object, NM_DEVICE_VXLAN_ID); + if (priv->props.group != props.group) + g_object_notify (object, NM_DEVICE_VXLAN_GROUP); + if (priv->props.local != props.local) + g_object_notify (object, NM_DEVICE_VXLAN_LOCAL); + if (memcmp (&priv->props.group6, &props.group6, sizeof (props.group6)) != 0) + g_object_notify (object, NM_DEVICE_VXLAN_GROUP); + if (memcmp (&priv->props.local6, &props.local6, sizeof (props.local6)) != 0) + g_object_notify (object, NM_DEVICE_VXLAN_LOCAL); + if (priv->props.tos != props.tos) + g_object_notify (object, NM_DEVICE_VXLAN_TOS); + if (priv->props.ttl != props.ttl) + g_object_notify (object, NM_DEVICE_VXLAN_TTL); + if (priv->props.learning != props.learning) + g_object_notify (object, NM_DEVICE_VXLAN_LEARNING); + if (priv->props.ageing != props.ageing) + g_object_notify (object, NM_DEVICE_VXLAN_AGEING); + if (priv->props.limit != props.limit) + g_object_notify (object, NM_DEVICE_VXLAN_LIMIT); + if (priv->props.dst_port != props.dst_port) + g_object_notify (object, NM_DEVICE_VXLAN_DST_PORT); + if (priv->props.src_port_min != props.src_port_min) + g_object_notify (object, NM_DEVICE_VXLAN_SRC_PORT_MIN); + if (priv->props.src_port_max != props.src_port_max) + g_object_notify (object, NM_DEVICE_VXLAN_SRC_PORT_MAX); + if (priv->props.proxy != props.proxy) + g_object_notify (object, NM_DEVICE_VXLAN_PROXY); + if (priv->props.rsc != props.rsc) + g_object_notify (object, NM_DEVICE_VXLAN_RSC); + if (priv->props.l2miss != props.l2miss) + g_object_notify (object, NM_DEVICE_VXLAN_L2MISS); + if (priv->props.l3miss != props.l3miss) + g_object_notify (object, NM_DEVICE_VXLAN_L3MISS); + + memcpy (&priv->props, &props, sizeof (NMPlatformVxlanProperties)); + + g_object_thaw_notify (object); +} + +static void +link_changed (NMDevice *device, NMPlatformLink *info) +{ + NM_DEVICE_CLASS (nm_device_vxlan_parent_class)->link_changed (device, info); + update_properties (device); +} + +/**************************************************************/ + +NMDevice * +nm_device_vxlan_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_VXLAN, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "Vxlan", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_GENERIC, + NULL); +} + +static void +nm_device_vxlan_init (NMDeviceVxlan *self) +{ +} + +static void +constructed (GObject *object) +{ + update_properties (NM_DEVICE (object)); + + G_OBJECT_CLASS (nm_device_vxlan_parent_class)->constructed (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE (object); + NMDevice *parent; + + switch (prop_id) { + case PROP_PARENT: + parent = nm_manager_get_device_by_ifindex (nm_manager_get (), priv->props.parent_ifindex); + g_value_set_boxed (value, parent ? nm_device_get_path (parent) : "/"); + break; + case PROP_ID: + g_value_set_uint (value, priv->props.id); + break; + case PROP_GROUP: + if (priv->props.group) + g_value_set_string (value, nm_utils_inet4_ntop (priv->props.group, NULL)); + else if (!IN6_IS_ADDR_UNSPECIFIED (&priv->props.group6)) + g_value_set_string (value, nm_utils_inet6_ntop (&priv->props.group6, NULL)); + break; + case PROP_LOCAL: + if (priv->props.local) + g_value_set_string (value, nm_utils_inet4_ntop (priv->props.local, NULL)); + else if (!IN6_IS_ADDR_UNSPECIFIED (&priv->props.local6)) + g_value_set_string (value, nm_utils_inet6_ntop (&priv->props.local6, NULL)); + break; + case PROP_TOS: + g_value_set_uchar (value, priv->props.tos); + break; + case PROP_TTL: + g_value_set_uchar (value, priv->props.ttl); + break; + case PROP_LEARNING: + g_value_set_boolean (value, priv->props.learning); + break; + case PROP_AGEING: + g_value_set_uint (value, priv->props.ageing); + break; + case PROP_LIMIT: + g_value_set_uint (value, priv->props.limit); + break; + case PROP_DST_PORT: + g_value_set_uint (value, priv->props.dst_port); + break; + case PROP_SRC_PORT_MIN: + g_value_set_uint (value, priv->props.src_port_min); + break; + case PROP_SRC_PORT_MAX: + g_value_set_uint (value, priv->props.src_port_max); + break; + case PROP_PROXY: + g_value_set_uint (value, priv->props.proxy); + break; + case PROP_RSC: + g_value_set_boolean (value, priv->props.rsc); + break; + case PROP_L2MISS: + g_value_set_boolean (value, priv->props.l2miss); + break; + case PROP_L3MISS: + g_value_set_boolean (value, priv->props.l3miss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_vxlan_class_init (NMDeviceVxlanClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMDeviceVxlanPrivate)); + + object_class->constructed = constructed; + object_class->get_property = get_property; + + device_class->link_changed = link_changed; + + /* properties */ + g_object_class_install_property + (object_class, PROP_PARENT, + g_param_spec_boxed (NM_DEVICE_VXLAN_PARENT, + "Parent", + "Parent device", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_ID, + g_param_spec_uint (NM_DEVICE_VXLAN_ID, + "Id", + "Id", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_GROUP, + g_param_spec_string (NM_DEVICE_VXLAN_GROUP, + "Group", + "Group", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_LOCAL, + g_param_spec_string (NM_DEVICE_VXLAN_LOCAL, + "Local", + "Local", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_TOS, + g_param_spec_uchar (NM_DEVICE_VXLAN_TOS, + "ToS", + "ToS", + 0, 255, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_TTL, + g_param_spec_uchar (NM_DEVICE_VXLAN_TTL, + "TTL", + "TTL", + 0, 255, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_LEARNING, + g_param_spec_boolean (NM_DEVICE_VXLAN_LEARNING, + "Learning", + "Learning", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_AGEING, + g_param_spec_uint (NM_DEVICE_VXLAN_AGEING, + "Ageing", + "Ageing", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_LIMIT, + g_param_spec_uint (NM_DEVICE_VXLAN_LIMIT, + "Limit", + "Limit", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_DST_PORT, + g_param_spec_uint (NM_DEVICE_VXLAN_DST_PORT, + "Destination port", + "Destination port", + 0, 65535, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_SRC_PORT_MIN, + g_param_spec_uint (NM_DEVICE_VXLAN_SRC_PORT_MIN, + "Source port min", + "Minimum source port", + 0, 65535, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_SRC_PORT_MAX, + g_param_spec_uint (NM_DEVICE_VXLAN_SRC_PORT_MAX, + "Source port max", + "Maximum source port", + 0, 65535, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_PROXY, + g_param_spec_boolean (NM_DEVICE_VXLAN_PROXY, + "Proxy", + "Proxy", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_RSC, + g_param_spec_boolean (NM_DEVICE_VXLAN_RSC, + "RSC", + "RSC", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_L2MISS, + g_param_spec_boolean (NM_DEVICE_VXLAN_L2MISS, + "L2miss", + "L2miss", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_L3MISS, + g_param_spec_boolean (NM_DEVICE_VXLAN_L3MISS, + "L3miss", + "L3miss", + FALSE, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_vxlan_object_info); +} diff --git a/src/devices/nm-device-vxlan.h b/src/devices/nm-device-vxlan.h new file mode 100644 index 000000000..14b158ded --- /dev/null +++ b/src/devices/nm-device-vxlan.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2013, 2014 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_VXLAN_H +#define NM_DEVICE_VXLAN_H + +#include <glib-object.h> + +#include "nm-device-generic.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_VXLAN (nm_device_vxlan_get_type ()) +#define NM_DEVICE_VXLAN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlan)) +#define NM_DEVICE_VXLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlanClass)) +#define NM_IS_DEVICE_VXLAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_VXLAN)) +#define NM_IS_DEVICE_VXLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_VXLAN)) +#define NM_DEVICE_VXLAN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlanClass)) + +#define NM_DEVICE_VXLAN_PARENT "parent" +#define NM_DEVICE_VXLAN_ID "id" +#define NM_DEVICE_VXLAN_GROUP "group" +#define NM_DEVICE_VXLAN_LOCAL "local" +#define NM_DEVICE_VXLAN_TOS "tos" +#define NM_DEVICE_VXLAN_TTL "ttl" +#define NM_DEVICE_VXLAN_LEARNING "learning" +#define NM_DEVICE_VXLAN_AGEING "ageing" +#define NM_DEVICE_VXLAN_LIMIT "limit" +#define NM_DEVICE_VXLAN_DST_PORT "dst-port" +#define NM_DEVICE_VXLAN_SRC_PORT_MIN "src-port-min" +#define NM_DEVICE_VXLAN_SRC_PORT_MAX "src-port-max" +#define NM_DEVICE_VXLAN_PROXY "proxy" +#define NM_DEVICE_VXLAN_RSC "rsc" +#define NM_DEVICE_VXLAN_L2MISS "l2miss" +#define NM_DEVICE_VXLAN_L3MISS "l3miss" + +typedef struct { + NMDeviceGeneric parent; +} NMDeviceVxlan; + +typedef struct { + NMDeviceGenericClass parent; + +} NMDeviceVxlanClass; + +GType nm_device_vxlan_get_type (void); + +NMDevice *nm_device_vxlan_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_VXLAN_H */ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c new file mode 100644 index 000000000..4788a604d --- /dev/null +++ b/src/devices/nm-device.c @@ -0,0 +1,7966 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2005 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include <config.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <dbus/dbus.h> +#include <netinet/in.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <linux/sockios.h> +#include <linux/ethtool.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <linux/if.h> +#include <netlink/route/addr.h> + +#include "libgsystem.h" +#include "nm-glib-compat.h" +#include "nm-device.h" +#include "nm-device-private.h" +#include "NetworkManagerUtils.h" +#include "nm-manager.h" +#include "nm-platform.h" +#include "nm-rdisc.h" +#include "nm-lndp-rdisc.h" +#include "nm-dhcp-manager.h" +#include "nm-dbus-manager.h" +#include "nm-utils.h" +#include "nm-logging.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-connection.h" +#include "nm-dnsmasq-manager.h" +#include "nm-dhcp4-config.h" +#include "nm-rfkill-manager.h" +#include "nm-firewall-manager.h" +#include "nm-properties-changed-signal.h" +#include "nm-enum-types.h" +#include "nm-settings-connection.h" +#include "nm-connection-provider.h" +#include "nm-posix-signals.h" +#include "nm-manager-auth.h" +#include "nm-dbus-glib-types.h" +#include "nm-dispatcher.h" +#include "nm-config-device.h" +#include "nm-config.h" +#include "nm-dns-manager.h" + +#include "nm-device-bridge.h" +#include "nm-device-bond.h" +#include "nm-device-team.h" + +static void impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context); + +#include "nm-device-glue.h" + +static void nm_device_config_device_interface_init (NMConfigDeviceInterface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMDevice, nm_device, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (NM_TYPE_CONFIG_DEVICE, nm_device_config_device_interface_init)) + +#define NM_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE, NMDevicePrivate)) + +enum { + STATE_CHANGED, + AUTOCONNECT_ALLOWED, + AUTH_REQUEST, + IP4_CONFIG_CHANGED, + IP6_CONFIG_CHANGED, + REMOVED, + RECHECK_AUTO_ACTIVATE, + RECHECK_ASSUME, + LAST_SIGNAL, +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +enum { + PROP_0, + PROP_PLATFORM_DEVICE, + PROP_UDI, + PROP_IFACE, + PROP_IP_IFACE, + PROP_DRIVER, + PROP_DRIVER_VERSION, + PROP_FIRMWARE_VERSION, + PROP_CAPABILITIES, + PROP_CARRIER, + PROP_MTU, + PROP_IP4_ADDRESS, + PROP_IP4_CONFIG, + PROP_DHCP4_CONFIG, + PROP_IP6_CONFIG, + PROP_DHCP6_CONFIG, + PROP_STATE, + PROP_STATE_REASON, + PROP_ACTIVE_CONNECTION, + PROP_DEVICE_TYPE, + PROP_MANAGED, + PROP_AUTOCONNECT, + PROP_FIRMWARE_MISSING, + PROP_TYPE_DESC, + PROP_RFKILL_TYPE, + PROP_IFINDEX, + PROP_AVAILABLE_CONNECTIONS, + PROP_PHYSICAL_PORT_ID, + PROP_IS_MASTER, + PROP_MASTER, + PROP_HW_ADDRESS, + PROP_HAS_PENDING_ACTION, + LAST_PROP +}; + +/***********************************************************/ + +#define PENDING_ACTION_DHCP4 "dhcp4" +#define PENDING_ACTION_DHCP6 "dhcp6" +#define PENDING_ACTION_AUTOCONF6 "autoconf6" + +typedef enum { + IP_NONE = 0, + IP_WAIT, + IP_CONF, + IP_DONE, + IP_FAIL +} IpState; + +typedef struct { + NMDeviceState state; + NMDeviceStateReason reason; + guint id; +} QueuedState; + +typedef struct { + NMDevice *slave; + gboolean enslaved; + gboolean configure; + guint watch_id; +} SlaveInfo; + +typedef struct { + guint log_domain; + guint timeout; + guint watch; + GPid pid; +} PingInfo; + +typedef struct { + NMDevice *device; + guint idle_add_id; + int ifindex; +} DeleteOnDeactivateData; + +typedef struct { + gboolean in_state_changed; + + NMDeviceState state; + NMDeviceStateReason state_reason; + QueuedState queued_state; + guint queued_ip_config_id; + GSList *pending_actions; + + char * udi; + char * path; + char * iface; /* may change, could be renamed by user */ + int ifindex; + gboolean is_software; + char * ip_iface; + int ip_ifindex; + NMDeviceType type; + char * type_desc; + guint32 capabilities; + char * driver; + char * driver_version; + char * firmware_version; + RfKillType rfkill_type; + gboolean firmware_missing; + GHashTable * available_connections; + guint8 hw_addr[NM_UTILS_HWADDR_LEN_MAX]; + guint hw_addr_len; + char * physical_port_id; + + NMUnmanagedFlags unmanaged_flags; + gboolean is_nm_owned; /* whether the device is a device owned and created by NM */ + DeleteOnDeactivateData *delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */ + + guint32 ip4_address; + + NMActRequest * queued_act_request; + NMActRequest * act_request; + guint act_source_id; + gpointer act_source_func; + guint act_source6_id; + gpointer act_source6_func; + guint recheck_assume_id; + struct { + guint call_id; + NMDeviceState post_state; + NMDeviceStateReason post_state_reason; + } dispatcher; + + /* Link stuff */ + guint link_connected_id; + guint link_disconnected_id; + guint carrier_defer_id; + gboolean carrier; + guint carrier_wait_id; + gboolean ignore_carrier; + guint32 mtu; + + /* Generic DHCP stuff */ + guint32 dhcp_timeout; + GByteArray * dhcp_anycast_address; + + /* IP4 configuration info */ + NMIP4Config * ip4_config; /* Combined config from VPN, settings, and device */ + IpState ip4_state; + NMIP4Config * dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */ + NMIP4Config * ext_ip4_config; /* Stuff added outside NM */ + + /* DHCPv4 tracking */ + NMDHCPClient * dhcp4_client; + gulong dhcp4_state_sigid; + gulong dhcp4_timeout_sigid; + NMDHCP4Config * dhcp4_config; + NMIP4Config * vpn4_config; /* routes added by a VPN which uses this device */ + + guint arp_round2_id; + PingInfo gw_ping; + + /* dnsmasq stuff for shared connections */ + NMDnsMasqManager *dnsmasq_manager; + gulong dnsmasq_state_id; + + /* Firewall Manager */ + NMFirewallManager *fw_manager; + DBusGProxyCall *fw_call; + + /* avahi-autoipd stuff */ + GPid aipd_pid; + guint aipd_watch; + guint aipd_timeout; + + /* IP6 configuration info */ + NMIP6Config * ip6_config; + IpState ip6_state; + NMIP6Config * vpn6_config; /* routes added by a VPN which uses this device */ + NMIP6Config * ext_ip6_config; /* Stuff added outside NM */ + + NMRDisc * rdisc; + gulong rdisc_config_changed_sigid; + NMSettingIP6ConfigPrivacy rdisc_use_tempaddr; + /* IP6 config from autoconf */ + NMIP6Config * ac_ip6_config; + + guint linklocal6_timeout_id; + + GHashTable * ip6_saved_properties; + + NMDHCPClient * dhcp6_client; + NMRDiscDHCPLevel dhcp6_mode; + gulong dhcp6_state_sigid; + gulong dhcp6_timeout_sigid; + NMDHCP6Config * dhcp6_config; + /* IP6 config from DHCP */ + NMIP6Config * dhcp6_ip6_config; + + /* allow autoconnect feature */ + gboolean autoconnect; + + /* master interface for bridge/bond/team slave */ + NMDevice * master; + gboolean enslaved; + guint master_ready_id; + + /* slave management */ + gboolean is_master; + GSList * slaves; /* list of SlaveInfo */ + + NMConnectionProvider *con_provider; +} NMDevicePrivate; + +static gboolean nm_device_set_ip4_config (NMDevice *dev, + NMIP4Config *config, + gboolean commit, + NMDeviceStateReason *reason); +static gboolean ip4_config_merge_and_apply (NMDevice *self, + NMIP4Config *config, + gboolean commit, + NMDeviceStateReason *out_reason); + +static gboolean nm_device_set_ip6_config (NMDevice *dev, + NMIP6Config *config, + gboolean commit, + NMDeviceStateReason *reason); + +static gboolean nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure); +static void nm_device_slave_notify_enslave (NMDevice *dev, gboolean success); +static void nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason); + +static void addrconf6_start_with_link_ready (NMDevice *self); + +static gboolean nm_device_get_default_unmanaged (NMDevice *device); + +static void _set_state_full (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason, + gboolean quitting); + +/***********************************************************/ + +static GQuark +nm_device_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-device-error"); + return quark; +} + +#define NM_DEVICE_ERROR (nm_device_error_quark ()) + +/***********************************************************/ + +#define QUEUED_PREFIX "queued state change to " + +static const char *state_table[] = { + [NM_DEVICE_STATE_UNKNOWN] = QUEUED_PREFIX "unknown", + [NM_DEVICE_STATE_UNMANAGED] = QUEUED_PREFIX "unmanaged", + [NM_DEVICE_STATE_UNAVAILABLE] = QUEUED_PREFIX "unavailable", + [NM_DEVICE_STATE_DISCONNECTED] = QUEUED_PREFIX "disconnected", + [NM_DEVICE_STATE_PREPARE] = QUEUED_PREFIX "prepare", + [NM_DEVICE_STATE_CONFIG] = QUEUED_PREFIX "config", + [NM_DEVICE_STATE_NEED_AUTH] = QUEUED_PREFIX "need-auth", + [NM_DEVICE_STATE_IP_CONFIG] = QUEUED_PREFIX "ip-config", + [NM_DEVICE_STATE_IP_CHECK] = QUEUED_PREFIX "ip-check", + [NM_DEVICE_STATE_SECONDARIES] = QUEUED_PREFIX "secondaries", + [NM_DEVICE_STATE_ACTIVATED] = QUEUED_PREFIX "activated", + [NM_DEVICE_STATE_DEACTIVATING] = QUEUED_PREFIX "deactivating", + [NM_DEVICE_STATE_FAILED] = QUEUED_PREFIX "failed", +}; + +static const char * +queued_state_to_string (NMDeviceState state) +{ + if (state >= 0 && state < G_N_ELEMENTS (state_table)) + return state_table[state]; + return state_table[NM_DEVICE_STATE_UNKNOWN]; +} + +static const char * +state_to_string (NMDeviceState state) +{ + return queued_state_to_string (state) + strlen (QUEUED_PREFIX); +} + +static const char *reason_table[] = { + [NM_DEVICE_STATE_REASON_NONE] = "none", + [NM_DEVICE_STATE_REASON_NOW_MANAGED] = "managed", + [NM_DEVICE_STATE_REASON_NOW_UNMANAGED] = "unmanaged", + [NM_DEVICE_STATE_REASON_CONFIG_FAILED] = "config-failed", + [NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE] = "ip-config-unavailable", + [NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED] = "ip-config-expired", + [NM_DEVICE_STATE_REASON_NO_SECRETS] = "no-secrets", + [NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT] = "supplicant-disconnect", + [NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED] = "supplicant-config-failed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED] = "supplicant-failed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT] = "supplicant-timeout", + [NM_DEVICE_STATE_REASON_PPP_START_FAILED] = "ppp-start-failed", + [NM_DEVICE_STATE_REASON_PPP_DISCONNECT] = "ppp-disconnect", + [NM_DEVICE_STATE_REASON_PPP_FAILED] = "ppp-failed", + [NM_DEVICE_STATE_REASON_DHCP_START_FAILED] = "dhcp-start-failed", + [NM_DEVICE_STATE_REASON_DHCP_ERROR] = "dhcp-error", + [NM_DEVICE_STATE_REASON_DHCP_FAILED] = "dhcp-failed", + [NM_DEVICE_STATE_REASON_SHARED_START_FAILED] = "sharing-start-failed", + [NM_DEVICE_STATE_REASON_SHARED_FAILED] = "sharing-failed", + [NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED] = "autoip-start-failed", + [NM_DEVICE_STATE_REASON_AUTOIP_ERROR] = "autoip-error", + [NM_DEVICE_STATE_REASON_AUTOIP_FAILED] = "autoip-failed", + [NM_DEVICE_STATE_REASON_MODEM_BUSY] = "modem-busy", + [NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE] = "modem-no-dialtone", + [NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER] = "modem-no-carrier", + [NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT] = "modem-dial-timeout", + [NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED] = "modem-dial-failed", + [NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED] = "modem-init-failed", + [NM_DEVICE_STATE_REASON_GSM_APN_FAILED] = "gsm-apn-failed", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING] = "gsm-registration-idle", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED] = "gsm-registration-denied", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT] = "gsm-registration-timeout", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED] = "gsm-registration-failed", + [NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED] = "gsm-pin-check-failed", + [NM_DEVICE_STATE_REASON_FIRMWARE_MISSING] = "firmware-missing", + [NM_DEVICE_STATE_REASON_REMOVED] = "removed", + [NM_DEVICE_STATE_REASON_SLEEPING] = "sleeping", + [NM_DEVICE_STATE_REASON_CONNECTION_REMOVED] = "connection-removed", + [NM_DEVICE_STATE_REASON_USER_REQUESTED] = "user-requested", + [NM_DEVICE_STATE_REASON_CARRIER] = "carrier-changed", + [NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED] = "connection-assumed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE] = "supplicant-available", + [NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND] = "modem-not-found", + [NM_DEVICE_STATE_REASON_BT_FAILED] = "bluetooth-failed", + [NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED] = "gsm-sim-not-inserted", + [NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED] = "gsm-sim-pin-required", + [NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED] = "gsm-sim-puk-required", + [NM_DEVICE_STATE_REASON_GSM_SIM_WRONG] = "gsm-sim-wrong", + [NM_DEVICE_STATE_REASON_INFINIBAND_MODE] = "infiniband-mode", + [NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED] = "dependency-failed", + [NM_DEVICE_STATE_REASON_BR2684_FAILED] = "br2684-bridge-failed", + [NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE] = "modem-manager-unavailable", + [NM_DEVICE_STATE_REASON_SSID_NOT_FOUND] = "ssid-not-found", + [NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED] = "secondary-connection-failed", + [NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED] = "dcb-fcoe-failed", + [NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED] = "teamd-control-failed", + [NM_DEVICE_STATE_REASON_MODEM_FAILED] = "modem-failed", + [NM_DEVICE_STATE_REASON_MODEM_AVAILABLE] = "modem-available", + [NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT] = "sim-pin-incorrect", +}; + +static const char * +reason_to_string (NMDeviceStateReason reason) +{ + if (reason >= 0 && reason < G_N_ELEMENTS (reason_table)) + return reason_table[reason]; + return reason_table[NM_DEVICE_STATE_REASON_UNKNOWN]; +} + +/***********************************************************/ + +static inline gboolean +nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value) +{ + return nm_platform_sysctl_set (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property), value); +} + +static gboolean +device_has_capability (NMDevice *device, NMDeviceCapabilities caps) +{ + return !!(NM_DEVICE_GET_PRIVATE (device)->capabilities & caps); +} + +/***********************************************************/ + +void +nm_device_dbus_export (NMDevice *device) +{ + static guint32 devcount = 0; + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (device)); + + priv = NM_DEVICE_GET_PRIVATE (device); + g_return_if_fail (priv->path == NULL); + + priv->path = g_strdup_printf ("/org/freedesktop/NetworkManager/Devices/%d", devcount++); + nm_log_info (LOGD_DEVICE, "(%s): exported as %s", priv->iface, priv->path); + nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, device); +} + +const char * +nm_device_get_path (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->path; +} + +const char * +nm_device_get_udi (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->udi; +} + +const char * +nm_device_get_iface (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->iface; +} + +int +nm_device_get_ifindex (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, 0); + + return NM_DEVICE_GET_PRIVATE (self)->ifindex; +} + +gboolean +nm_device_is_software (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + return priv->is_software; +} + +const char * +nm_device_get_ip_iface (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail (self != NULL, NULL); + + priv = NM_DEVICE_GET_PRIVATE (self); + /* If it's not set, default to iface */ + return priv->ip_iface ? priv->ip_iface : priv->iface; +} + +int +nm_device_get_ip_ifindex (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail (self != NULL, 0); + + priv = NM_DEVICE_GET_PRIVATE (self); + /* If it's not set, default to iface */ + return priv->ip_iface ? priv->ip_ifindex : priv->ifindex; +} + +void +nm_device_set_ip_iface (NMDevice *self, const char *iface) +{ + NMDevicePrivate *priv; + char *old_ip_iface; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + if (!g_strcmp0 (iface, priv->ip_iface)) + return; + + old_ip_iface = priv->ip_iface; + priv->ip_ifindex = 0; + + priv->ip_iface = g_strdup (iface); + if (priv->ip_iface) { + priv->ip_ifindex = nm_platform_link_get_ifindex (priv->ip_iface); + if (priv->ip_ifindex > 0) { + if (!nm_platform_link_is_up (priv->ip_ifindex)) + nm_platform_link_set_up (priv->ip_ifindex); + } else { + /* Device IP interface must always be a kernel network interface */ + nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", iface); + } + } + + /* We don't care about any saved values from the old iface */ + g_hash_table_remove_all (priv->ip6_saved_properties); + + /* Emit change notification */ + if (g_strcmp0 (old_ip_iface, priv->ip_iface)) + g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE); + g_free (old_ip_iface); +} + +const char * +nm_device_get_driver (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->driver; +} + +const char * +nm_device_get_driver_version (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->driver_version; +} + +NMDeviceType +nm_device_get_device_type (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_TYPE_UNKNOWN); + + return NM_DEVICE_GET_PRIVATE (self)->type; +} + + +/** + * nm_device_get_priority(): + * @dev: the #NMDevice + * + * Returns: the device's routing priority. Lower numbers means a "better" + * device, eg higher priority. + */ +int +nm_device_get_priority (NMDevice *dev) +{ + g_return_val_if_fail (NM_IS_DEVICE (dev), 100); + + /* Device 'priority' is used for two things: + * + * a) two devices on the same IP subnet: the "better" (ie, lower number) + * device is the default outgoing device for that subnet + * b) default route: the "better" device gets the default route. This can + * always be modified by setting a connection to never-default=TRUE, in + * which case that device will never take the default route when + * it's using that connection. + */ + + switch (nm_device_get_device_type (dev)) { + case NM_DEVICE_TYPE_ETHERNET: + return 1; + case NM_DEVICE_TYPE_INFINIBAND: + return 2; + case NM_DEVICE_TYPE_ADSL: + return 3; + case NM_DEVICE_TYPE_WIMAX: + return 4; + case NM_DEVICE_TYPE_BOND: + return 5; + case NM_DEVICE_TYPE_TEAM: + return 6; + case NM_DEVICE_TYPE_VLAN: + return 7; + case NM_DEVICE_TYPE_MODEM: + return 8; + case NM_DEVICE_TYPE_BT: + return 9; + case NM_DEVICE_TYPE_WIFI: + return 10; + case NM_DEVICE_TYPE_OLPC_MESH: + return 11; + default: + return 20; + } +} + +const char * +nm_device_get_type_desc (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->type_desc; +} + +gboolean +nm_device_has_carrier (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->carrier; +} + +NMActRequest * +nm_device_get_act_request (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->act_request; +} + +NMConnection * +nm_device_get_connection (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + return priv->act_request ? nm_act_request_get_connection (priv->act_request) : NULL; +} + +RfKillType +nm_device_get_rfkill_type (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + return NM_DEVICE_GET_PRIVATE (self)->rfkill_type; +} + +static const char * +nm_device_get_physical_port_id (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->physical_port_id; +} + +/***********************************************************/ + +static gboolean +nm_device_uses_generated_connection (NMDevice *self) +{ + NMConnection *connection; + + connection = nm_device_get_connection (self); + if (!connection) + return FALSE; + return nm_settings_connection_get_nm_generated (NM_SETTINGS_CONNECTION (connection)); +} + +static SlaveInfo * +find_slave_info (NMDevice *self, NMDevice *slave) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + SlaveInfo *info; + GSList *iter; + + for (iter = priv->slaves; iter; iter = g_slist_next (iter)) { + info = iter->data; + if (info->slave == slave) + return info; + } + return NULL; +} + +static void +free_slave_info (SlaveInfo *info) +{ + g_signal_handler_disconnect (info->slave, info->watch_id); + g_clear_object (&info->slave); + memset (info, 0, sizeof (*info)); + g_free (info); +} + +/** + * nm_device_enslave_slave: + * @dev: the master device + * @slave: the slave device to enslave + * @connection: (allow-none): the slave device's connection + * + * If @dev is capable of enslaving other devices (ie it's a bridge, bond, team, + * etc) then this function enslaves @slave. + * + * Returns: %TRUE on success, %FALSE on failure or if this device cannot enslave + * other devices. + */ +static gboolean +nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connection) +{ + SlaveInfo *info; + gboolean success = FALSE; + gboolean configure; + + g_return_val_if_fail (dev != NULL, FALSE); + g_return_val_if_fail (slave != NULL, FALSE); + g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE); + + info = find_slave_info (dev, slave); + if (!info) + return FALSE; + + if (info->enslaved) + success = TRUE; + else { + configure = (info->configure && connection != NULL); + if (configure) + g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); + + success = NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave, connection, configure); + info->enslaved = success; + } + + nm_device_slave_notify_enslave (info->slave, success); + + /* Ensure the device's hardware address is up-to-date; it often changes + * when slaves change. + */ + nm_device_update_hw_address (dev); + + /* Restart IP configuration if we're waiting for slaves. Do this + * after updating the hardware address as IP config may need the + * new address. + */ + if (success) { + if (NM_DEVICE_GET_PRIVATE (dev)->ip4_state == IP_WAIT) + nm_device_activate_stage3_ip4_start (dev); + + if (NM_DEVICE_GET_PRIVATE (dev)->ip6_state == IP_WAIT) + nm_device_activate_stage3_ip6_start (dev); + } + + return success; +} + +/** + * nm_device_release_one_slave: + * @dev: the master device + * @slave: the slave device to release + * @configure: whether @dev needs to actually release @slave + * @reason: the state change reason for the @slave + * + * If @dev is capable of enslaving other devices (ie it's a bridge, bond, team, + * etc) then this function releases the previously enslaved @slave and/or + * updates the state of @dev and @slave to reflect its release. + * + * Returns: %TRUE on success, %FALSE on failure, if this device cannot enslave + * other devices, or if @slave was never enslaved. + */ +static gboolean +nm_device_release_one_slave (NMDevice *dev, NMDevice *slave, gboolean configure, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + SlaveInfo *info; + gboolean success = FALSE; + + g_return_val_if_fail (slave != NULL, FALSE); + g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->release_slave != NULL, FALSE); + + info = find_slave_info (dev, slave); + if (!info) + return FALSE; + priv->slaves = g_slist_remove (priv->slaves, info); + + if (info->enslaved) { + success = NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave, configure); + /* The release_slave() implementation logs success/failure (in the + * correct device-specific log domain), so we don't have to do anything. + */ + } + + if (!configure) { + g_warn_if_fail (reason == NM_DEVICE_STATE_REASON_NONE); + reason = NM_DEVICE_STATE_REASON_NONE; + } else if (reason == NM_DEVICE_STATE_REASON_NONE) { + g_warn_if_reached (); + reason = NM_DEVICE_STATE_REASON_UNKNOWN; + } + nm_device_slave_notify_release (info->slave, reason); + + free_slave_info (info); + + /* Ensure the device's hardware address is up-to-date; it often changes + * when slaves change. + */ + nm_device_update_hw_address (dev); + + return success; +} + +static void +carrier_changed (NMDevice *device, gboolean carrier) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + if (!nm_device_get_managed (device)) + return; + + nm_device_recheck_available_connections (device); + + /* ignore-carrier devices ignore all carrier-down events */ + if (priv->ignore_carrier && !carrier) + return; + + if (priv->is_master) { + /* Bridge/bond/team carrier does not affect its own activation, + * but when carrier comes on, if there are slaves waiting, + * it will restart them. + */ + if (!carrier) + return; + + if (nm_device_activate_ip4_state_in_wait (device)) + nm_device_activate_stage3_ip4_start (device); + if (nm_device_activate_ip6_state_in_wait (device)) + nm_device_activate_stage3_ip6_start (device); + + return; + } else if (nm_device_get_enslaved (device) && !carrier) { + /* Slaves don't deactivate when they lose carrier; for + * bonds/teams in particular that would be actively + * counterproductive. + */ + return; + } + + if (carrier) { + g_warn_if_fail (priv->state >= NM_DEVICE_STATE_UNAVAILABLE); + + if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) { + nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_CARRIER); + } else if (priv->state == NM_DEVICE_STATE_DISCONNECTED) { + /* If the device is already in DISCONNECTED state without a carrier + * (probably because it is tagged for carrier ignore) ensure that + * when the carrier appears, auto connections are rechecked for + * the device. + */ + nm_device_emit_recheck_auto_activate (device); + } + } else { + if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) { + if (nm_device_queued_state_peek (device) >= NM_DEVICE_STATE_DISCONNECTED) + nm_device_queued_state_clear (device); + } else if (priv->state >= NM_DEVICE_STATE_DISCONNECTED) { + nm_device_queue_state (device, NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_CARRIER); + } + } +} + +#define LINK_DISCONNECT_DELAY 4 + +static gboolean +link_disconnect_action_cb (gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (calling deferred action) (id=%u)", + nm_device_get_iface (device), priv->carrier_defer_id); + + priv->carrier_defer_id = 0; + + nm_log_info (LOGD_DEVICE, "(%s): link disconnected (calling deferred action)", + nm_device_get_iface (device)); + + NM_DEVICE_GET_CLASS (device)->carrier_changed (device, FALSE); + + return FALSE; +} + +static void +link_disconnect_action_cancel (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->carrier_defer_id) { + g_source_remove (priv->carrier_defer_id); + nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (canceling deferred action) (id=%u)", + nm_device_get_iface (self), priv->carrier_defer_id); + priv->carrier_defer_id = 0; + } +} + +void +nm_device_set_carrier (NMDevice *device, gboolean carrier) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device); + NMDeviceState state = nm_device_get_state (device); + const char *iface = nm_device_get_iface (device); + + if (priv->carrier == carrier) + return; + + priv->carrier = carrier; + g_object_notify (G_OBJECT (device), NM_DEVICE_CARRIER); + + if (priv->carrier) { + nm_log_info (LOGD_DEVICE, "(%s): link connected", iface); + link_disconnect_action_cancel (device); + klass->carrier_changed (device, TRUE); + + if (priv->carrier_wait_id) { + g_source_remove (priv->carrier_wait_id); + priv->carrier_wait_id = 0; + nm_device_remove_pending_action (device, "carrier wait", TRUE); + } + } else if (state <= NM_DEVICE_STATE_DISCONNECTED) { + nm_log_info (LOGD_DEVICE, "(%s): link disconnected", iface); + klass->carrier_changed (device, FALSE); + } else { + nm_log_info (LOGD_DEVICE, "(%s): link disconnected (deferring action for %d seconds)", + iface, LINK_DISCONNECT_DELAY); + priv->carrier_defer_id = g_timeout_add_seconds (LINK_DISCONNECT_DELAY, + link_disconnect_action_cb, device); + nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (deferring action for %d seconds) (id=%u)", + iface, LINK_DISCONNECT_DELAY, priv->carrier_defer_id); + } +} + +static void +update_for_ip_ifname_change (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + g_hash_table_remove_all (priv->ip6_saved_properties); + + if (priv->dhcp4_client) { + if (!nm_device_dhcp4_renew (device, FALSE)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_FAILED); + return; + } + } + if (priv->dhcp6_client) { + if (!nm_device_dhcp6_renew (device, FALSE)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_FAILED); + return; + } + } + if (priv->rdisc) { + /* FIXME: todo */ + } + if (priv->dnsmasq_manager) { + /* FIXME: todo */ + } +} + +static void +device_link_changed (NMDevice *device, NMPlatformLink *info) +{ + NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + gboolean ip_ifname_changed = FALSE; + + if (info->udi && g_strcmp0 (info->udi, priv->udi)) { + /* Update UDI to what udev gives us */ + g_free (priv->udi); + priv->udi = g_strdup (info->udi); + g_object_notify (G_OBJECT (device), NM_DEVICE_UDI); + } + + /* Update MTU if it has changed. */ + if (priv->mtu != info->mtu) { + priv->mtu = info->mtu; + g_object_notify (G_OBJECT (device), NM_DEVICE_MTU); + } + + if (info->name[0] && strcmp (priv->iface, info->name) != 0) { + nm_log_info (LOGD_DEVICE, "(%s): interface index %d renamed iface from '%s' to '%s'", + priv->iface, priv->ifindex, priv->iface, info->name); + g_free (priv->iface); + priv->iface = g_strdup (info->name); + + /* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */ + ip_ifname_changed = !priv->ip_iface; + + g_object_notify (G_OBJECT (device), NM_DEVICE_IFACE); + if (ip_ifname_changed) + g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE); + + /* Re-match available connections against the new interface name */ + nm_device_recheck_available_connections (device); + + /* Let any connections that use the new interface name have a chance + * to auto-activate on the device. + */ + nm_device_emit_recheck_auto_activate (device); + } + + /* Update slave status for external changes */ + if (info->master && !priv->enslaved) { + NMDevice *master; + + master = nm_manager_get_device_by_ifindex (nm_manager_get (), info->master); + if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave) { + g_clear_object (&priv->master); + priv->master = g_object_ref (master); + nm_device_master_add_slave (master, device, FALSE); + nm_device_enslave_slave (master, device, NULL); + } else if (master) { + nm_log_info (LOGD_DEVICE, "(%s): enslaved to non-master-type device %s; ignoring", + nm_device_get_iface (device), + nm_device_get_iface (master)); + } else { + nm_log_warn (LOGD_DEVICE, "(%s): enslaved to unknown device %d %s", + nm_device_get_iface (device), + info->master, + nm_platform_link_get_name (info->master)); + } + } else if (priv->enslaved && !info->master) + nm_device_release_one_slave (priv->master, device, FALSE, NM_DEVICE_STATE_REASON_NONE); + + if (klass->link_changed) + klass->link_changed (device, info); + + + /* Update DHCP, etc, if needed */ + if (ip_ifname_changed) + update_for_ip_ifname_change (device); +} + +static void +device_ip_link_changed (NMDevice *device, NMPlatformLink *info) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + if (info->name[0] && g_strcmp0 (priv->ip_iface, info->name)) { + nm_log_info (LOGD_DEVICE, "(%s): interface index %d renamed ip_iface (%d) from '%s' to '%s'", + priv->iface, priv->ifindex, nm_device_get_ip_ifindex (device), + priv->ip_iface, info->name); + g_free (priv->ip_iface); + priv->ip_iface = g_strdup (info->name); + + g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE); + update_for_ip_ifname_change (device); + } +} + +static void +link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDevice *device) +{ + if (change_type != NM_PLATFORM_SIGNAL_CHANGED) + return; + + /* We don't filter by 'reason' because we are interested in *all* link + * changes. For example a call to nm_platform_link_set_up() may result + * in an internal carrier change (i.e. we ask the kernel to set IFF_UP + * and it results in also setting IFF_LOWER_UP. + */ + + if (ifindex == nm_device_get_ifindex (device)) + device_link_changed (device, info); + else if (ifindex == nm_device_get_ip_ifindex (device)) + device_ip_link_changed (device, info); +} + +static void +link_changed (NMDevice *device, NMPlatformLink *info) +{ + /* Update carrier from link event if applicable. */ + if ( device_has_capability (device, NM_DEVICE_CAP_CARRIER_DETECT) + && !device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) + nm_device_set_carrier (device, info->connected); +} + +/** + * nm_device_notify_component_added(): + * @device: the #NMDevice + * @component: the component being added by a plugin + * + * Called by the manager to notify the device that a new component has + * been found. The device implementation should return %TRUE if it + * wishes to claim the component, or %FALSE if it cannot. + * + * Returns: %TRUE to claim the component, %FALSE if the component cannot be + * claimed. + */ +gboolean +nm_device_notify_component_added (NMDevice *device, GObject *component) +{ + if (NM_DEVICE_GET_CLASS (device)->component_added) + return NM_DEVICE_GET_CLASS (device)->component_added (device, component); + return FALSE; +} + +/** + * nm_device_owns_iface(): + * @device: the #NMDevice + * @iface: an interface name + * + * Called by the manager to ask if the device or any of its components owns + * @iface. For example, a WWAN implementation would return %TRUE for an + * ethernet interface name that was owned by the WWAN device's modem component, + * because that ethernet interface is controlled by the WWAN device and cannot + * be used independently of the WWAN device. + * + * Returns: %TRUE if @device or it's components owns the interface name, + * %FALSE if not + */ +gboolean +nm_device_owns_iface (NMDevice *device, const char *iface) +{ + if (NM_DEVICE_GET_CLASS (device)->owns_iface) + return NM_DEVICE_GET_CLASS (device)->owns_iface (device, iface); + return FALSE; +} + +static void +slave_state_changed (NMDevice *slave, + NMDeviceState slave_new_state, + NMDeviceState slave_old_state, + NMDeviceStateReason reason, + NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + gboolean release = FALSE; + + nm_log_dbg (LOGD_DEVICE, "(%s): slave %s state change %d (%s) -> %d (%s)", + nm_device_get_iface (self), + nm_device_get_iface (slave), + slave_old_state, + state_to_string (slave_old_state), + slave_new_state, + state_to_string (slave_new_state)); + + /* Don't try to enslave slaves until the master is ready */ + if (priv->state < NM_DEVICE_STATE_CONFIG) + return; + + if (slave_new_state == NM_DEVICE_STATE_IP_CONFIG) + nm_device_enslave_slave (self, slave, nm_device_get_connection (slave)); + else if (slave_new_state > NM_DEVICE_STATE_ACTIVATED) + release = TRUE; + else if ( slave_new_state <= NM_DEVICE_STATE_DISCONNECTED + && slave_old_state > NM_DEVICE_STATE_DISCONNECTED) { + /* Catch failures due to unavailable or unmanaged */ + release = TRUE; + } + + if (release) { + nm_device_release_one_slave (self, slave, TRUE, reason); + /* Bridge/bond/team interfaces are left up until manually deactivated */ + if (priv->slaves == NULL && priv->state == NM_DEVICE_STATE_ACTIVATED) { + nm_log_dbg (LOGD_DEVICE, "(%s): last slave removed; remaining activated", + nm_device_get_iface (self)); + } + } +} + +/** + * nm_device_master_add_slave: + * @dev: the master device + * @slave: the slave device to enslave + * @configure: pass %TRUE if the slave should be configured by the master, or + * %FALSE if it is already configured outside NetworkManager + * + * If @dev is capable of enslaving other devices (ie it's a bridge, bond, team, + * etc) then this function adds @slave to the slave list for later enslavement. + * + * Returns: %TRUE on success, %FALSE on failure + */ +static gboolean +nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + SlaveInfo *info; + + g_return_val_if_fail (dev != NULL, FALSE); + g_return_val_if_fail (slave != NULL, FALSE); + g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE); + + if (configure) + g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); + + if (!find_slave_info (dev, slave)) { + info = g_malloc0 (sizeof (SlaveInfo)); + info->slave = g_object_ref (slave); + info->configure = configure; + info->watch_id = g_signal_connect (slave, "state-changed", + G_CALLBACK (slave_state_changed), dev); + priv->slaves = g_slist_append (priv->slaves, info); + } + + return TRUE; +} + + +/** + * nm_device_master_get_slaves: + * @dev: the master device + * + * Returns: any slaves of which @device is the master. Caller owns returned list. + */ +GSList * +nm_device_master_get_slaves (NMDevice *dev) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + GSList *slaves = NULL, *iter; + + for (iter = priv->slaves; iter; iter = g_slist_next (iter)) + slaves = g_slist_prepend (slaves, ((SlaveInfo *) iter->data)->slave); + + return slaves; +} + +/** + * nm_device_master_get_slave_by_ifindex: + * @dev: the master device + * @ifindex: the slave's interface index + * + * Returns: the slave with the given @ifindex of which @device is the master, + * or %NULL if no device with @ifindex is a slave of @device. + */ +NMDevice * +nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex) +{ + GSList *iter; + + for (iter = NM_DEVICE_GET_PRIVATE (dev)->slaves; iter; iter = g_slist_next (iter)) { + SlaveInfo *info = iter->data; + + if (nm_device_get_ip_ifindex (info->slave) == ifindex) + return info->slave; + } + return NULL; +} + +/** + * nm_device_master_check_slave_physical_port: + * @dev: the master device + * @slave: a slave device + * @log_domain: domain to log a warning in + * + * Checks if @dev already has a slave with the same #NMDevice:physical-port-id + * as @slave, and logs a warning if so. + */ +void +nm_device_master_check_slave_physical_port (NMDevice *dev, NMDevice *slave, + guint64 log_domain) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + const char *slave_physical_port_id, *existing_physical_port_id; + SlaveInfo *info; + GSList *iter; + + slave_physical_port_id = nm_device_get_physical_port_id (slave); + if (!slave_physical_port_id) + return; + + for (iter = priv->slaves; iter; iter = iter->next) { + info = iter->data; + if (info->slave == slave) + continue; + + existing_physical_port_id = nm_device_get_physical_port_id (info->slave); + if (!g_strcmp0 (slave_physical_port_id, existing_physical_port_id)) { + nm_log_warn (log_domain, "(%s): slave %s shares a physical port with existing slave %s", + nm_device_get_ip_iface (dev), + nm_device_get_ip_iface (slave), + nm_device_get_ip_iface (info->slave)); + /* Since this function will get called for every slave, we only have + * to warn about the first match we find; if there are other matches + * later in the list, we will have already warned about them matching + * @existing earlier. + */ + return; + } + } +} + +/* release all slaves */ +static void +nm_device_master_release_slaves (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceStateReason reason; + + /* Don't release the slaves if this connection doesn't belong to NM. */ + if (nm_device_uses_generated_connection (self)) + return; + + reason = priv->state_reason; + if (priv->state == NM_DEVICE_STATE_FAILED) + reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; + + while (priv->slaves) { + SlaveInfo *info = priv->slaves->data; + + nm_device_release_one_slave (self, info->slave, TRUE, reason); + } +} + +/** + * nm_device_get_master: + * @dev: the device + * + * If @dev has been enslaved by another device, this returns that + * device. Otherwise it returns %NULL. (In particular, note that if + * @dev is in the process of activating as a slave, but has not yet + * been enslaved by its master, this will return %NULL.) + * + * Returns: (transfer none): @dev's master, or %NULL + */ +NMDevice * +nm_device_get_master (NMDevice *dev) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + + if (priv->enslaved) + return priv->master; + else + return NULL; +} + +/** + * nm_device_slave_notify_enslave: + * @dev: the slave device + * @success: whether the enslaving operation succeeded + * + * Notifies a slave that either it has been enslaved, or else its master tried + * to enslave it and failed. + */ +static void +nm_device_slave_notify_enslave (NMDevice *dev, gboolean success) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + NMConnection *connection = nm_device_get_connection (dev); + gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG); + + g_assert (priv->master); + + if (!priv->enslaved) { + if (success) { + if (activating) { + nm_log_info (LOGD_DEVICE, + "Activation (%s) connection '%s' enslaved, continuing activation", + nm_device_get_iface (dev), + nm_connection_get_id (connection)); + } else { + nm_log_info (LOGD_DEVICE, + "(%s): enslaved to %s", + nm_device_get_iface (dev), + nm_device_get_iface (priv->master)); + } + + priv->enslaved = TRUE; + g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER); + } else if (activating) { + nm_log_warn (LOGD_DEVICE, + "Activation (%s) connection '%s' could not be enslaved", + nm_device_get_iface (dev), + nm_connection_get_id (connection)); + } + } + + if (activating) { + priv->ip4_state = IP_DONE; + priv->ip6_state = IP_DONE; + nm_device_queue_state (dev, + success ? NM_DEVICE_STATE_SECONDARIES : NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NONE); + } else + nm_device_queue_recheck_assume (dev); +} + +/** + * nm_device_slave_notify_release: + * @dev: the slave device + * @reason: the reason associated with the state change + * + * Notifies a slave that it has been released, and why. + */ +static void +nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + NMConnection *connection = nm_device_get_connection (dev); + NMDeviceState new_state; + const char *master_status; + + if ( reason != NM_DEVICE_STATE_REASON_NONE + && priv->state > NM_DEVICE_STATE_DISCONNECTED + && priv->state <= NM_DEVICE_STATE_ACTIVATED) { + if (reason == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) { + new_state = NM_DEVICE_STATE_FAILED; + master_status = "failed"; + } else if (reason == NM_DEVICE_STATE_REASON_USER_REQUESTED) { + new_state = NM_DEVICE_STATE_DEACTIVATING; + master_status = "deactivated by user request"; + } else { + new_state = NM_DEVICE_STATE_DISCONNECTED; + master_status = "deactivated"; + } + + nm_log_dbg (LOGD_DEVICE, + "Activation (%s) connection '%s' master %s", + nm_device_get_iface (dev), + nm_connection_get_id (connection), + master_status); + + nm_device_queue_state (dev, new_state, reason); + } else { + nm_log_info (LOGD_DEVICE, + "(%s): released from master %s", + nm_device_get_iface (dev), + nm_device_get_iface (priv->master)); + } + + if (priv->enslaved) { + priv->enslaved = FALSE; + g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER); + } +} + +/** + * nm_device_get_enslaved: + * @device: the #NMDevice + * + * Returns: %TRUE if the device is enslaved to a master device (eg bridge or + * bond or team), %FALSE if not + */ +gboolean +nm_device_get_enslaved (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->enslaved; +} + +static gboolean +is_available (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + return priv->carrier || priv->ignore_carrier; +} + +/** + * nm_device_is_available: + * @self: the #NMDevice + * + * Checks if @self would currently be capable of activating a + * connection. In particular, it checks that the device is ready (eg, + * is not missing firmware), that it has carrier (if necessary), and + * that any necessary external software (eg, ModemManager, + * wpa_supplicant) is available. + * + * @self can only be in a state higher than + * %NM_DEVICE_STATE_UNAVAILABLE when nm_device_is_available() returns + * %TRUE. (But note that it can still be %NM_DEVICE_STATE_UNMANAGED + * when it is available.) + * + * Returns: %TRUE or %FALSE + */ +gboolean +nm_device_is_available (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->firmware_missing) + return FALSE; + + return NM_DEVICE_GET_CLASS (self)->is_available (self); +} + +gboolean +nm_device_get_enabled (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + if (NM_DEVICE_GET_CLASS (self)->get_enabled) + return NM_DEVICE_GET_CLASS (self)->get_enabled (self); + return TRUE; +} + +void +nm_device_set_enabled (NMDevice *self, gboolean enabled) +{ + g_return_if_fail (NM_IS_DEVICE (self)); + + if (NM_DEVICE_GET_CLASS (self)->set_enabled) + NM_DEVICE_GET_CLASS (self)->set_enabled (self, enabled); +} + +gboolean +nm_device_get_autoconnect (NMDevice *device) +{ + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + return NM_DEVICE_GET_PRIVATE (device)->autoconnect; +} + +static gboolean +autoconnect_allowed_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, gpointer data) +{ + if (!g_value_get_boolean (handler_return)) + g_value_set_boolean (return_accu, FALSE); + return TRUE; +} + +gboolean +nm_device_autoconnect_allowed (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + GValue instance = G_VALUE_INIT; + GValue retval = G_VALUE_INIT; + + g_value_init (&instance, G_TYPE_OBJECT); + g_value_set_object (&instance, self); + + g_value_init (&retval, G_TYPE_BOOLEAN); + if (priv->autoconnect) + g_value_set_boolean (&retval, TRUE); + else + g_value_set_boolean (&retval, FALSE); + + /* Use g_signal_emitv() rather than g_signal_emit() to avoid the return + * value being changed if no handlers are connected */ + g_signal_emitv (&instance, signals[AUTOCONNECT_ALLOWED], 0, &retval); + g_value_unset (&instance); + return g_value_get_boolean (&retval); +} + +static gboolean +can_auto_connect (NMDevice *device, + NMConnection *connection, + char **specific_object) +{ + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection (connection); + if (!nm_setting_connection_get_autoconnect (s_con)) + return FALSE; + + return nm_device_connection_is_available (device, connection, FALSE); +} + +static gboolean +device_has_config (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + /* Check for IP configuration. */ + if (priv->ip4_config && nm_ip4_config_get_num_addresses (priv->ip4_config)) + return TRUE; + if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config)) + return TRUE; + + /* The existence of a software device is good enough. */ + if (nm_device_is_software (device)) + return TRUE; + + /* Slaves are also configured by definition */ + if (nm_platform_link_get_master (priv->ifindex) > 0) + return TRUE; + + return FALSE; +} + +NMConnection * +nm_device_generate_connection (NMDevice *device) +{ + NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + const char *ifname = nm_device_get_iface (device); + int ifindex = nm_device_get_ifindex (device); + NMConnection *connection; + NMSetting *s_con; + NMSetting *s_ip4; + NMSetting *s_ip6; + gs_free char *uuid = NULL; + gs_free char *name = NULL; + int master_ifindex = 0; + const char *ip4_method, *ip6_method; + GError *error = NULL; + + /* If update_connection() is not implemented, just fail. */ + if (!klass->update_connection) + return NULL; + + /* Return NULL if device is unconfigured. */ + if (!device_has_config (device)) { + nm_log_dbg (LOGD_DEVICE, "(%s): device has no existing configuration", ifname); + return NULL; + } + + if (ifindex) + master_ifindex = nm_platform_link_get_master (ifindex); + if (master_ifindex) { + NMDevice *master; + + master = nm_manager_get_device_by_ifindex (nm_manager_get (), master_ifindex); + if (!master || !nm_device_get_act_request (master)) { + nm_log_dbg (LOGD_DEVICE, "(%s): cannot generate connection for slave before its master (%s)", + ifname, nm_platform_link_get_name (master_ifindex)); + return NULL; + } + } + + connection = nm_connection_new (); + s_con = nm_setting_connection_new (); + uuid = nm_utils_uuid_generate (); + name = g_strdup_printf ("%s", ifname); + + g_object_set (s_con, + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_ID, name, + NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, + NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, + NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL), + NULL); + if (klass->connection_type) + g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, klass->connection_type, NULL); + nm_connection_add_setting (connection, s_con); + + /* If the device is a slave, update various slave settings */ + if (master_ifindex) { + const char *master_iface = nm_platform_link_get_name (master_ifindex); + const char *slave_type = NULL; + gboolean success = FALSE; + + switch (nm_platform_link_get_type (master_ifindex)) { + case NM_LINK_TYPE_BRIDGE: + slave_type = NM_SETTING_BRIDGE_SETTING_NAME; + success = nm_bridge_update_slave_connection (device, connection); + break; + case NM_LINK_TYPE_BOND: + slave_type = NM_SETTING_BOND_SETTING_NAME; + success = TRUE; + break; + case NM_LINK_TYPE_TEAM: + slave_type = NM_SETTING_TEAM_SETTING_NAME; + success = nm_team_update_slave_connection (device, connection); + break; + default: + g_warn_if_reached (); + break; + } + + if (!success) + nm_log_err (LOGD_DEVICE, "(%s): failed to read slave configuration", ifname); + + g_object_set (s_con, + NM_SETTING_CONNECTION_MASTER, master_iface, + NM_SETTING_CONNECTION_SLAVE_TYPE, slave_type, + NULL); + } else { + /* Only regular and master devices get IP configuration; slaves do not */ + s_ip4 = nm_ip4_config_create_setting (priv->ip4_config); + nm_connection_add_setting (connection, s_ip4); + + s_ip6 = nm_ip6_config_create_setting (priv->ip6_config); + nm_connection_add_setting (connection, s_ip6); + } + + klass->update_connection (device, connection); + + /* Check the connection in case of update_connection() bug. */ + if (!nm_connection_verify (connection, &error)) { + nm_log_err (LOGD_DEVICE, "(%s): Generated connection does not verify: %s", + nm_device_get_iface (device), error->message); + g_clear_error (&error); + g_object_unref (connection); + return NULL; + } + + /* Ignore the connection if it has no IP configuration, + * no slave configuration, and is not a master interface. + */ + ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + ip6_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + if ( g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0 + && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0 + && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))) { + nm_log_dbg (LOGD_DEVICE, "(%s): ignoring generated connection (no IP and not slave)", ifname); + g_object_unref (connection); + connection = NULL; + } + + return connection; +} + +/** + * nm_device_get_best_auto_connection: + * @dev: an #NMDevice + * @connections: (element-type #NMConnection): a list of connections + * @specific_object: (out) (transfer full): on output, the path of an + * object associated with the returned connection, to be passed to + * nm_manager_activate_connection(), or %NULL. + * + * Looks through @connections to see if there is a connection that can + * be auto-activated on @dev right now. This requires, at a minimum, + * that the connection be compatible with @dev, and that it have the + * #NMSettingConnection:autoconnect property set. Some devices impose + * additional requirements. (Eg, a Wi-Fi connection can only be + * activated if its SSID was seen in the last scan.) + * + * Returns: an auto-activatable #NMConnection, or %NULL if none are + * available. + */ + +NMConnection * +nm_device_get_best_auto_connection (NMDevice *dev, + GSList *connections, + char **specific_object) +{ + GSList *iter; + + g_return_val_if_fail (NM_IS_DEVICE (dev), NULL); + g_return_val_if_fail (specific_object != NULL, NULL); + g_return_val_if_fail (*specific_object == NULL, NULL); + + for (iter = connections; iter; iter = iter->next) { + NMConnection *connection = NM_CONNECTION (iter->data); + + if (NM_DEVICE_GET_CLASS (dev)->can_auto_connect (dev, connection, specific_object)) + return connection; + } + + return NULL; +} + +gboolean +nm_device_complete_connection (NMDevice *self, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + gboolean success = FALSE; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (connection != NULL, FALSE); + + if (!NM_DEVICE_GET_CLASS (self)->complete_connection) { + g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CONNECTION_INVALID, + "Device class %s had no complete_connection method", + G_OBJECT_TYPE_NAME (self)); + return FALSE; + } + + success = NM_DEVICE_GET_CLASS (self)->complete_connection (self, + connection, + specific_object, + existing_connections, + error); + if (success) + success = nm_connection_verify (connection, error); + + return success; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMSettingConnection *s_con; + const char *config_iface, *device_iface; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + config_iface = nm_setting_connection_get_interface_name (s_con); + device_iface = nm_device_get_iface (device); + if (config_iface && strcmp (config_iface, device_iface) != 0) + return FALSE; + + return TRUE; +} + +/** + * nm_device_check_connection_compatible: + * @device: an #NMDevice + * @connection: an #NMConnection + * + * Checks if @connection could potentially be activated on @device. + * This means only that @device has the proper capabilities, and that + * @connection is not locked to some other device. It does not + * necessarily mean that @connection could be activated on @device + * right now. (Eg, it might refer to a Wi-Fi network that is not + * currently available.) + * + * Returns: #TRUE if @connection could potentially be activated on + * @device. + */ +gboolean +nm_device_check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + + return NM_DEVICE_GET_CLASS (device)->check_connection_compatible (device, connection); +} + +static gboolean +string_in_list (const char *str, const char **array, gsize array_len) +{ + gsize i; + + for (i = 0; i < array_len; i++) { + if (strcmp (str, array[i]) == 0) + return TRUE; + } + return FALSE; +} + +/** + * nm_device_can_assume_connections: + * @device: #NMDevice instance + * + * This is a convenience function to determine whether connection assumption + * is available for this device. + * + * Returns: %TRUE if the device is capable of assuming connections, %FALSE if not + */ +static gboolean +nm_device_can_assume_connections (NMDevice *device) +{ + return !!NM_DEVICE_GET_CLASS (device)->update_connection; +} + +/** + * nm_device_can_assume_active_connection: + * @device: #NMDevice instance + * + * This is a convenience function to determine whether the device's active + * connection can be assumed if NetworkManager restarts. This method returns + * %TRUE if and only if the device can assume connections, and the device has + * an active connection, and that active connection can be assumed. + * + * Returns: %TRUE if the device's active connection can be assumed, or %FALSE + * if there is no active connection or the active connection cannot be + * assumed. + */ +gboolean +nm_device_can_assume_active_connection (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMConnection *connection; + const char *method; + const char *assumable_ip6_methods[] = { + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + }; + const char *assumable_ip4_methods[] = { + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + }; + + if (!nm_device_can_assume_connections (device)) + return FALSE; + + connection = nm_device_get_connection (device); + if (!connection) + return FALSE; + + /* Can't assume connections that aren't yet configured + * FIXME: what about bridges/bonds waiting for slaves? + */ + if (priv->state < NM_DEVICE_STATE_IP_CONFIG) + return FALSE; + if (priv->ip4_state != IP_DONE && priv->ip6_state != IP_DONE) + return FALSE; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + if (!string_in_list (method, assumable_ip6_methods, G_N_ELEMENTS (assumable_ip6_methods))) + return FALSE; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (!string_in_list (method, assumable_ip4_methods, G_N_ELEMENTS (assumable_ip4_methods))) + return FALSE; + + return TRUE; +} + +static gboolean +nm_device_emit_recheck_assume (gpointer self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->recheck_assume_id = 0; + if (!nm_device_get_act_request (self) && (priv->ip4_config || priv->ip6_config)) + g_signal_emit (self, signals[RECHECK_ASSUME], 0); + return G_SOURCE_REMOVE; +} + +void +nm_device_queue_recheck_assume (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (nm_device_can_assume_connections (self) && !priv->recheck_assume_id) + priv->recheck_assume_id = g_idle_add (nm_device_emit_recheck_assume, self); +} + +void +nm_device_emit_recheck_auto_activate (NMDevice *self) +{ + g_signal_emit (self, signals[RECHECK_AUTO_ACTIVATE], 0); +} + +static void +dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + + switch (status) { + case NM_DNSMASQ_STATUS_DEAD: + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED); + break; + default: + break; + } +} + +static void +activation_source_clear (NMDevice *self, gboolean remove_source, int family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + guint *act_source_id; + gpointer *act_source_func; + + if (family == AF_INET6) { + act_source_id = &priv->act_source6_id; + act_source_func = &priv->act_source6_func; + } else { + act_source_id = &priv->act_source_id; + act_source_func = &priv->act_source_func; + } + + if (*act_source_id) { + if (remove_source) + g_source_remove (*act_source_id); + *act_source_id = 0; + *act_source_func = NULL; + } +} + +static void +activation_source_schedule (NMDevice *self, GSourceFunc func, int family) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + guint *act_source_id; + gpointer *act_source_func; + + if (family == AF_INET6) { + act_source_id = &priv->act_source6_id; + act_source_func = &priv->act_source6_func; + } else { + act_source_id = &priv->act_source_id; + act_source_func = &priv->act_source_func; + } + + if (*act_source_id) { + nm_log_err (LOGD_DEVICE, "activation stage already scheduled"); + } + + /* Don't bother rescheduling the same function that's about to + * run anyway. Fixes issues with crappy wireless drivers sending + * streams of associate events before NM has had a chance to process + * the first one. + */ + if (!*act_source_id || (*act_source_func != func)) { + activation_source_clear (self, TRUE, family); + *act_source_id = g_idle_add (func, self); + *act_source_func = func; + } +} + +gboolean +nm_device_ip_config_should_fail (NMDevice *self, gboolean ip6) +{ + NMConnection *connection; + NMSettingIP4Config *s_ip4; + NMSettingIP6Config *s_ip6; + + g_return_val_if_fail (self != NULL, TRUE); + + connection = nm_device_get_connection (self); + g_assert (connection); + + /* Fail the connection if the failed IP method is required to complete */ + if (ip6) { + s_ip6 = nm_connection_get_setting_ip6_config (connection); + if (!nm_setting_ip6_config_get_may_fail (s_ip6)) + return TRUE; + } else { + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (!nm_setting_ip4_config_get_may_fail (s_ip4)) + return TRUE; + } + + return FALSE; +} + +static void +master_ready_cb (NMActiveConnection *active, + GParamSpec *pspec, + NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActiveConnection *master; + + g_assert (priv->state == NM_DEVICE_STATE_PREPARE); + + /* Notify a master device that it has a new slave */ + g_assert (nm_active_connection_get_master_ready (active)); + master = nm_active_connection_get_master (active); + + priv->master = g_object_ref (nm_active_connection_get_device (master)); + nm_device_master_add_slave (priv->master, + self, + nm_active_connection_get_assumed (active) ? FALSE : TRUE); + + nm_log_dbg (LOGD_DEVICE, "(%s): master connection ready; master device %s", + nm_device_get_iface (self), + nm_device_get_iface (priv->master)); + + if (priv->master_ready_id) { + g_signal_handler_disconnect (active, priv->master_ready_id); + priv->master_ready_id = 0; + } + + nm_device_activate_schedule_stage2_device_config (self); +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason) +{ + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +/* + * nm_device_activate_stage1_device_prepare + * + * Prepare for device activation + * + */ +static gboolean +nm_device_activate_stage1_device_prepare (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request); + + /* Clear the activation source ID now that this stage has run */ + activation_source_clear (self, FALSE, 0); + + priv->ip4_state = priv->ip6_state = IP_NONE; + + /* Notify the new ActiveConnection along with the state change */ + g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION); + + iface = nm_device_get_iface (self); + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) started...", iface); + nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE); + + /* Assumed connections were already set up outside NetworkManager */ + if (!nm_active_connection_get_assumed (active)) { + ret = NM_DEVICE_GET_CLASS (self)->act_stage1_prepare (self, &reason); + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) { + goto out; + } else if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + goto out; + } + g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS); + } + + if (nm_active_connection_get_master (active)) { + /* If the master connection is ready for slaves, attach ourselves */ + if (nm_active_connection_get_master_ready (active)) + master_ready_cb (active, NULL, self); + else { + nm_log_dbg (LOGD_DEVICE, "(%s): waiting for master connection to become ready", + nm_device_get_iface (self)); + + /* Attach a signal handler and wait for the master connection to begin activating */ + g_assert (priv->master_ready_id == 0); + priv->master_ready_id = g_signal_connect (active, + "notify::" NM_ACTIVE_CONNECTION_INT_MASTER_READY, + (GCallback) master_ready_cb, + self); + /* Postpone */ + } + } else + nm_device_activate_schedule_stage2_device_config (self); + +out: + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) complete.", iface); + return FALSE; +} + + +/* + * nm_device_activate_schedule_stage1_device_prepare + * + * Prepare a device for activation + * + */ +void +nm_device_activate_schedule_stage1_device_prepare (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + g_return_if_fail (priv->act_request); + + activation_source_schedule (self, nm_device_activate_stage1_device_prepare, 0); + + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) scheduled...", + nm_device_get_iface (self)); +} + +static NMActStageReturn +act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason) +{ + /* Nothing to do */ + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +/* + * nm_device_activate_stage2_device_config + * + * Determine device parameters and set those on the device, ie + * for wireless devices, set SSID, keys, etc. + * + */ +static gboolean +nm_device_activate_stage2_device_config (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface; + NMActStageReturn ret; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + gboolean no_firmware = FALSE; + NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request); + GSList *iter; + + /* Clear the activation source ID now that this stage has run */ + activation_source_clear (self, FALSE, 0); + + iface = nm_device_get_iface (self); + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) starting...", iface); + nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); + + /* Assumed connections were already set up outside NetworkManager */ + if (!nm_active_connection_get_assumed (active)) { + if (!nm_device_bring_up (self, FALSE, &no_firmware)) { + if (no_firmware) + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING); + else + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + goto out; + } + + ret = NM_DEVICE_GET_CLASS (self)->act_stage2_config (self, &reason); + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) + goto out; + else if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + goto out; + } + g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS); + } + + /* If we have slaves that aren't yet enslaved, do that now */ + for (iter = priv->slaves; iter; iter = g_slist_next (iter)) { + SlaveInfo *info = iter->data; + NMDeviceState slave_state = nm_device_get_state (info->slave); + + if (slave_state == NM_DEVICE_STATE_IP_CONFIG) + nm_device_enslave_slave (self, info->slave, nm_device_get_connection (info->slave)); + else if ( nm_device_uses_generated_connection (self) + && slave_state <= NM_DEVICE_STATE_DISCONNECTED) + nm_device_queue_recheck_assume (info->slave); + } + + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) successful.", iface); + + nm_device_activate_schedule_stage3_ip_config_start (self); + +out: + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) complete.", iface); + return FALSE; +} + + +/* + * nm_device_activate_schedule_stage2_device_config + * + * Schedule setup of the hardware device + * + */ +void +nm_device_activate_schedule_stage2_device_config (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + g_return_if_fail (priv->act_request); + + activation_source_schedule (self, nm_device_activate_stage2_device_config, 0); + + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) scheduled...", + nm_device_get_iface (self)); +} + +/*********************************************/ +/* avahi-autoipd stuff */ + +static void +aipd_timeout_remove (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->aipd_timeout) { + g_source_remove (priv->aipd_timeout); + priv->aipd_timeout = 0; + } +} + +static void +aipd_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->aipd_watch) { + g_source_remove (priv->aipd_watch); + priv->aipd_watch = 0; + } + + if (priv->aipd_pid > 0) { + kill (priv->aipd_pid, SIGKILL); + + /* ensure the child is reaped */ + nm_log_dbg (LOGD_AUTOIP4, "waiting for avahi-autoipd pid %d to exit", priv->aipd_pid); + waitpid (priv->aipd_pid, NULL, 0); + nm_log_dbg (LOGD_AUTOIP4, "avahi-autoip pid %d cleaned up", priv->aipd_pid); + + priv->aipd_pid = -1; + } + + aipd_timeout_remove (self); +} + +static NMIP4Config * +aipd_get_ip4_config (NMDevice *self, guint32 lla) +{ + NMIP4Config *config = NULL; + NMPlatformIP4Address address; + NMPlatformIP4Route route; + + config = nm_ip4_config_new (); + g_assert (config); + + memset (&address, 0, sizeof (address)); + address.address = lla; + address.plen = 16; + address.source = NM_PLATFORM_SOURCE_IP4LL; + nm_ip4_config_add_address (config, &address); + + /* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */ + memset (&route, 0, sizeof (route)); + route.network = htonl (0xE0000000L); + route.plen = 4; + route.source = NM_PLATFORM_SOURCE_IP4LL; + route.metric = nm_device_get_priority (self); + nm_ip4_config_add_route (config, &route); + + return config; +} + +#define IPV4LL_NETWORK (htonl (0xA9FE0000L)) +#define IPV4LL_NETMASK (htonl (0xFFFF0000L)) + +void +nm_device_handle_autoip4_event (NMDevice *self, + const char *event, + const char *address) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection = NULL; + const char *iface, *method; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + g_return_if_fail (event != NULL); + + if (priv->act_request == NULL) + return; + + connection = nm_act_request_get_connection (priv->act_request); + g_assert (connection); + + /* Ignore if the connection isn't an AutoIP connection */ + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) != 0) + return; + + iface = nm_device_get_iface (self); + + if (strcmp (event, "BIND") == 0) { + guint32 lla; + NMIP4Config *config; + + if (inet_pton (AF_INET, address, &lla) <= 0) { + nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd.", + iface, address); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR); + return; + } + + if ((lla & IPV4LL_NETMASK) != IPV4LL_NETWORK) { + nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd (not link-local).", + iface, address); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR); + return; + } + + config = aipd_get_ip4_config (self, lla); + if (config == NULL) { + nm_log_err (LOGD_AUTOIP4, "failed to get autoip config"); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } + + if (priv->ip4_state == IP_CONF) { + aipd_timeout_remove (self); + nm_device_activate_schedule_ip4_config_result (self, config); + } else if (priv->ip4_state == IP_DONE) { + if (!ip4_config_merge_and_apply (self, config, TRUE, &reason)) { + nm_log_err (LOGD_AUTOIP4, "(%s): failed to update IP4 config for autoip change.", + nm_device_get_iface (self)); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + } + } else + g_assert_not_reached (); + + g_object_unref (config); + } else { + nm_log_warn (LOGD_AUTOIP4, "(%s): autoip address %s no longer valid because '%s'.", + iface, address, event); + + /* The address is gone; terminate the connection or fail activation */ + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); + } +} + +static void +aipd_watch_cb (GPid pid, gint status, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceState state; + const char *iface; + + if (!priv->aipd_watch) + return; + priv->aipd_watch = 0; + + iface = nm_device_get_iface (self); + + if (WIFEXITED (status)) { + nm_log_dbg (LOGD_AUTOIP4, "(%s): avahi-autoipd exited with error code %d", + iface, WEXITSTATUS (status)); + } else if (WIFSTOPPED (status)) { + nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd stopped unexpectedly with signal %d", + iface, WSTOPSIG (status)); + } else if (WIFSIGNALED (status)) { + nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died with signal %d", + iface, WTERMSIG (status)); + } else { + nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died from an unknown cause", iface); + } + + aipd_cleanup (self); + + state = nm_device_get_state (self); + if (nm_device_is_activating (self) || (state == NM_DEVICE_STATE_ACTIVATED)) + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_FAILED); +} + +static gboolean +aipd_timeout_cb (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->aipd_timeout) { + nm_log_info (LOGD_AUTOIP4, "(%s): avahi-autoipd timed out.", nm_device_get_iface (self)); + priv->aipd_timeout = 0; + aipd_cleanup (self); + + if (priv->ip4_state == IP_CONF) + nm_device_activate_schedule_ip4_config_timeout (self); + } + + return FALSE; +} + +static void +aipd_child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process at this point. + * Give child it's own program group for signal + * separation. + */ + pid_t pid = getpid (); + setpgid (pid, pid); + + /* + * We blocked signals in main(). We need to restore original signal + * mask for avahi-autoipd here so that it can receive signals. + */ + nm_unblock_posix_signals (NULL); +} + +/* default to installed helper, but can be modified for testing */ +const char *nm_device_autoipd_helper_path = LIBEXECDIR "/nm-avahi-autoipd.action"; + +static NMActStageReturn +aipd_start (NMDevice *self, NMDeviceStateReason *reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface = nm_device_get_iface (self); + char *argv[6], *cmdline; + const char **aipd_binary = NULL; + static const char *aipd_paths[] = { + "/usr/sbin/avahi-autoipd", + "/usr/local/sbin/avahi-autoipd", + NULL + }; + int i = 0; + GError *error = NULL; + + aipd_cleanup (self); + + /* Find avahi-autoipd */ + aipd_binary = aipd_paths; + while (*aipd_binary != NULL) { + if (g_file_test (*aipd_binary, G_FILE_TEST_EXISTS)) + break; + aipd_binary++; + } + + if (!*aipd_binary) { + nm_log_warn (LOGD_DEVICE | LOGD_AUTOIP4, + "Activation (%s) Stage 3 of 5 (IP Configure Start) failed" + " to start avahi-autoipd: not found", iface); + *reason = NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + argv[i++] = (char *) (*aipd_binary); + argv[i++] = "--script"; + argv[i++] = (char *) nm_device_autoipd_helper_path; + + if (nm_logging_enabled (LOGL_DEBUG, LOGD_AUTOIP4)) + argv[i++] = "--debug"; + argv[i++] = (char *) nm_device_get_ip_iface (self); + argv[i++] = NULL; + + cmdline = g_strjoinv (" ", argv); + nm_log_dbg (LOGD_AUTOIP4, "running: %s", cmdline); + g_free (cmdline); + + if (!g_spawn_async ("/", argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, + &aipd_child_setup, NULL, &(priv->aipd_pid), &error)) { + nm_log_warn (LOGD_DEVICE | LOGD_AUTOIP4, + "Activation (%s) Stage 3 of 5 (IP Configure Start) failed" + " to start avahi-autoipd: %s", + iface, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + aipd_cleanup (self); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + nm_log_info (LOGD_DEVICE | LOGD_AUTOIP4, + "Activation (%s) Stage 3 of 5 (IP Configure Start) started" + " avahi-autoipd...", iface); + + /* Monitor the child process so we know when it dies */ + priv->aipd_watch = g_child_watch_add (priv->aipd_pid, aipd_watch_cb, self); + + /* Start a timeout to bound the address attempt */ + priv->aipd_timeout = g_timeout_add_seconds (20, aipd_timeout_cb, self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*********************************************/ +/* DHCPv4 stuff */ + +static void +dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dhcp4_client) { + /* Stop any ongoing DHCP transaction on this device */ + if (priv->dhcp4_state_sigid) { + g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_state_sigid); + priv->dhcp4_state_sigid = 0; + } + + if (priv->dhcp4_timeout_sigid) { + g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_timeout_sigid); + priv->dhcp4_timeout_sigid = 0; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE); + + if (stop) + nm_dhcp_client_stop (priv->dhcp4_client, release); + + g_clear_object (&priv->dhcp4_client); + } + + if (priv->dhcp4_config) { + g_clear_object (&priv->dhcp4_config); + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG); + } +} + +static void +dhcp4_add_option_cb (gpointer key, gpointer value, gpointer user_data) +{ + nm_dhcp4_config_add_option (NM_DHCP4_CONFIG (user_data), + (const char *) key, + (const char *) value); +} + +static gboolean +ip4_config_merge_and_apply (NMDevice *self, + NMIP4Config *config, + gboolean commit, + NMDeviceStateReason *out_reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + gboolean success; + NMIP4Config *composite; + + /* Merge all the configs into the composite config */ + if (config) { + g_clear_object (&priv->dev_ip4_config); + priv->dev_ip4_config = g_object_ref (config); + } + + composite = nm_ip4_config_new (); + if (priv->dev_ip4_config) + nm_ip4_config_merge (composite, priv->dev_ip4_config); + if (priv->vpn4_config) + nm_ip4_config_merge (composite, priv->vpn4_config); + if (priv->ext_ip4_config) + nm_ip4_config_merge (composite, priv->ext_ip4_config); + + /* Merge user overrides into the composite config */ + connection = nm_device_get_connection (self); + if (connection) { + nm_ip4_config_merge_setting (composite, + nm_connection_get_setting_ip4_config (connection), + nm_device_get_priority (self)); + } + + /* Allow setting MTU etc */ + if (commit) { + if (NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit) + NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite); + } + + success = nm_device_set_ip4_config (self, composite, commit, out_reason); + g_object_unref (composite); + return success; +} + +static void +dhcp4_lease_change (NMDevice *self, NMIP4Config *config) +{ + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + g_return_if_fail (config != NULL); + + if (!ip4_config_merge_and_apply (self, config, TRUE, &reason)) { + nm_log_warn (LOGD_DHCP4, "(%s): failed to update IPv4 config for DHCP change.", + nm_device_get_ip_iface (self)); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + } else { + /* Notify dispatcher scripts of new DHCP4 config */ + nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE, + nm_device_get_connection (self), + self, + NULL, + NULL, + NULL); + } +} + +static void +dhcp4_fail (NMDevice *device, gboolean timeout) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + nm_dhcp4_config_reset (priv->dhcp4_config); + + if (timeout || (priv->ip4_state == IP_CONF)) + nm_device_activate_schedule_ip4_config_timeout (device); + else if (priv->ip4_state == IP_FAIL) + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); +} + +static void +dhcp4_state_changed (NMDHCPClient *client, + NMDHCPState state, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMIP4Config *config; + + g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE); + + nm_log_dbg (LOGD_DHCP4, "(%s): new DHCPv4 client state %d", + nm_device_get_iface (device), state); + + switch (state) { + case DHC_BOUND4: /* lease obtained */ + case DHC_RENEW4: /* lease renewed */ + case DHC_REBOOT: /* have valid lease, but now obtained a different one */ + case DHC_REBIND4: /* new, different lease */ + config = nm_dhcp_client_get_ip4_config (priv->dhcp4_client, FALSE); + if (!config) { + nm_log_warn (LOGD_DHCP4, "(%s): failed to get IPv4 config in response to DHCP event.", + nm_device_get_ip_iface (device)); + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + break; + } + + /* Update the DHCP4 config object with new DHCP options */ + nm_dhcp4_config_reset (priv->dhcp4_config); + nm_dhcp_client_foreach_option (priv->dhcp4_client, + dhcp4_add_option_cb, + priv->dhcp4_config); + g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG); + + if (priv->ip4_state == IP_CONF) + nm_device_activate_schedule_ip4_config_result (device, config); + else if (priv->ip4_state == IP_DONE) + dhcp4_lease_change (device, config); + g_object_unref (config); + + break; + case DHC_TIMEOUT: /* timed out contacting DHCP server */ + dhcp4_fail (device, TRUE); + break; + case DHC_END: /* dhclient exited normally */ + case DHC_FAIL: /* all attempts to contact server timed out, sleeping */ + case DHC_ABEND: /* dhclient exited abnormally */ + /* dhclient quit and can't get/renew a lease; so kill the connection */ + dhcp4_fail (device, FALSE); + break; + default: + break; + } +} + +static void +dhcp4_timeout (NMDHCPClient *client, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + g_return_if_fail (nm_device_get_act_request (device) != NULL); + g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE); + + nm_dhcp_client_stop (client, FALSE); + dhcp4_fail (device, TRUE); +} + +static NMActStageReturn +dhcp4_start (NMDevice *self, + NMConnection *connection, + NMDeviceStateReason *reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMSettingIP4Config *s_ip4; + GByteArray *tmp = NULL; + + s_ip4 = nm_connection_get_setting_ip4_config (connection); + + /* Clear old exported DHCP options */ + if (priv->dhcp4_config) + g_object_unref (priv->dhcp4_config); + priv->dhcp4_config = nm_dhcp4_config_new (); + + if (priv->hw_addr_len) { + tmp = g_byte_array_sized_new (priv->hw_addr_len); + g_byte_array_append (tmp, priv->hw_addr, priv->hw_addr_len); + } + + /* Begin DHCP on the interface */ + g_warn_if_fail (priv->dhcp4_client == NULL); + priv->dhcp4_client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (), + nm_device_get_ip_iface (self), + tmp, + nm_connection_get_uuid (connection), + nm_device_get_priority (self), + s_ip4, + priv->dhcp_timeout, + priv->dhcp_anycast_address); + + if (tmp) + g_byte_array_free (tmp, TRUE); + + if (!priv->dhcp4_client) { + *reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv->dhcp4_state_sigid = g_signal_connect (priv->dhcp4_client, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK (dhcp4_state_changed), + self); + priv->dhcp4_timeout_sigid = g_signal_connect (priv->dhcp4_client, + NM_DHCP_CLIENT_SIGNAL_TIMEOUT, + G_CALLBACK (dhcp4_timeout), + self); + + nm_device_add_pending_action (self, PENDING_ACTION_DHCP4, TRUE); + + /* DHCP devices will be notified by the DHCP manager when stuff happens */ + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +gboolean +nm_device_dhcp4_renew (NMDevice *self, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActStageReturn ret; + NMDeviceStateReason reason; + NMConnection *connection; + + g_return_val_if_fail (priv->dhcp4_client != NULL, FALSE); + + nm_log_info (LOGD_DHCP4, "(%s): DHCPv4 lease renewal requested", + nm_device_get_iface (self)); + + /* Terminate old DHCP instance and release the old lease */ + dhcp4_cleanup (self, TRUE, release); + + connection = nm_device_get_connection (self); + g_assert (connection); + + /* Start DHCP again on the interface */ + ret = dhcp4_start (self, connection, &reason); + + return (ret != NM_ACT_STAGE_RETURN_FAILURE); +} + +/*********************************************/ + +static GHashTable *shared_ips = NULL; + +static void +release_shared_ip (gpointer data) +{ + g_hash_table_remove (shared_ips, data); +} + +static gboolean +reserve_shared_ip (NMSettingIP4Config *s_ip4, NMPlatformIP4Address *address) +{ + if (G_UNLIKELY (shared_ips == NULL)) + shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal); + + memset (address, 0, sizeof (*address)); + + if (s_ip4 && nm_setting_ip4_config_get_num_addresses (s_ip4)) { + /* Use the first user-supplied address */ + NMIP4Address *user = nm_setting_ip4_config_get_address (s_ip4, 0); + + g_assert (user); + address->address = nm_ip4_address_get_address (user); + address->plen = nm_ip4_address_get_prefix (user); + } else { + /* Find an unused address in the 10.42.x.x range */ + guint32 start = (guint32) ntohl (0x0a2a0001); /* 10.42.0.1 */ + guint32 count = 0; + + while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) { + count += ntohl (0x100); + if (count > ntohl (0xFE00)) { + nm_log_err (LOGD_SHARING, "ran out of shared IP addresses!"); + return FALSE; + } + } + address->address = start + count; + address->plen = 24; + + g_hash_table_insert (shared_ips, + GUINT_TO_POINTER (address->address), + GUINT_TO_POINTER (TRUE)); + } + + return TRUE; +} + +static NMIP4Config * +shared4_new_config (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason) +{ + NMIP4Config *config = NULL; + NMPlatformIP4Address address; + + g_return_val_if_fail (self != NULL, NULL); + + if (!reserve_shared_ip (nm_connection_get_setting_ip4_config (connection), &address)) { + *reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE; + return NULL; + } + + config = nm_ip4_config_new (); + address.source = NM_PLATFORM_SOURCE_SHARED; + nm_ip4_config_add_address (config, &address); + + /* Remove the address lock when the object gets disposed */ + g_object_set_data_full (G_OBJECT (config), "shared-ip", + GUINT_TO_POINTER (address.address), + release_shared_ip); + + return config; +} + +/*********************************************/ + +static gboolean +have_any_ready_slaves (NMDevice *device, const GSList *slaves) +{ + const GSList *iter; + + /* Any enslaved slave is "ready" in the generic case as it's + * at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2 + * properties set up. + */ + for (iter = slaves; iter; iter = g_slist_next (iter)) { + if (nm_device_get_enslaved (iter->data)) + return TRUE; + } + return FALSE; +} + +static gboolean +ip4_requires_slaves (NMConnection *connection) +{ + const char *method; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + return strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0; +} + +static NMActStageReturn +act_stage3_ip4_config_start (NMDevice *self, + NMIP4Config **out_config, + NMDeviceStateReason *reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + const char *method; + GSList *slaves; + gboolean ready_slaves; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_device_get_connection (self); + g_assert (connection); + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (priv->master) + g_assert_cmpstr (method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED); + + if ( strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) != 0 + && priv->is_master + && !priv->carrier) { + nm_log_info (LOGD_IP4 | LOGD_DEVICE, + "(%s): IPv4 config waiting until carrier is on", + nm_device_get_ip_iface (self)); + return NM_ACT_STAGE_RETURN_WAIT; + } + + if (priv->is_master && ip4_requires_slaves (connection)) { + /* If the master has no ready slaves, and depends on slaves for + * a successful IPv4 attempt, then postpone IPv4 addressing. + */ + slaves = nm_device_master_get_slaves (self); + ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves); + g_slist_free (slaves); + + if (ready_slaves == FALSE) { + nm_log_info (LOGD_DEVICE | LOGD_IP4, + "(%s): IPv4 config waiting until slaves are ready", + nm_device_get_ip_iface (self)); + return NM_ACT_STAGE_RETURN_WAIT; + } + } + + /* Start IPv4 addressing based on the method requested */ + if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) + ret = dhcp4_start (self, connection, reason); + else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0) + ret = aipd_start (self, reason); + else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) { + /* Use only IPv4 config from the connection data */ + *out_config = nm_ip4_config_new (); + g_assert (*out_config); + ret = NM_ACT_STAGE_RETURN_SUCCESS; + } else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) { + *out_config = shared4_new_config (self, connection, reason); + if (*out_config) { + priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self)); + ret = NM_ACT_STAGE_RETURN_SUCCESS; + } else + ret = NM_ACT_STAGE_RETURN_FAILURE; + } else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0) { + /* Nothing to do... */ + ret = NM_ACT_STAGE_RETURN_STOP; + } else { + nm_log_warn (LOGD_IP4, "(%s): unhandled IPv4 config method '%s'; will fail", + nm_device_get_ip_iface (self), method); + } + + return ret; +} + +/*********************************************/ +/* DHCPv6 stuff */ + +static void +dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE; + g_clear_object (&priv->dhcp6_ip6_config); + + if (priv->dhcp6_client) { + if (priv->dhcp6_state_sigid) { + g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_state_sigid); + priv->dhcp6_state_sigid = 0; + } + + if (priv->dhcp6_timeout_sigid) { + g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_timeout_sigid); + priv->dhcp6_timeout_sigid = 0; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE); + + if (stop) + nm_dhcp_client_stop (priv->dhcp6_client, release); + + g_clear_object (&priv->dhcp6_client); + } + + if (priv->dhcp6_config) { + g_clear_object (&priv->dhcp6_config); + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); + } +} + +static void +dhcp6_add_option_cb (gpointer key, gpointer value, gpointer user_data) +{ + nm_dhcp6_config_add_option (NM_DHCP6_CONFIG (user_data), + (const char *) key, + (const char *) value); +} + +static gboolean +ip6_config_merge_and_apply (NMDevice *self, + gboolean commit, + NMDeviceStateReason *out_reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + gboolean success; + NMIP6Config *composite; + + /* If no config was passed in, create a new one */ + composite = nm_ip6_config_new (); + g_assert (composite); + + /* Merge all the IP configs into the composite config */ + if (priv->ac_ip6_config) + nm_ip6_config_merge (composite, priv->ac_ip6_config); + if (priv->dhcp6_ip6_config) + nm_ip6_config_merge (composite, priv->dhcp6_ip6_config); + if (priv->vpn6_config) + nm_ip6_config_merge (composite, priv->vpn6_config); + if (priv->ext_ip6_config) + nm_ip6_config_merge (composite, priv->ext_ip6_config); + + /* Merge user overrides into the composite config */ + connection = nm_device_get_connection (self); + if (connection) { + nm_ip6_config_merge_setting (composite, + nm_connection_get_setting_ip6_config (connection), + nm_device_get_priority (self)); + } + + nm_ip6_config_addresses_sort (composite, + priv->rdisc ? priv->rdisc_use_tempaddr : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + + success = nm_device_set_ip6_config (self, composite, commit, out_reason); + g_object_unref (composite); + return success; +} + +static void +dhcp6_lease_change (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMConnection *connection; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + if (priv->dhcp6_ip6_config == NULL) { + nm_log_warn (LOGD_DHCP6, "(%s): failed to get DHCPv6 config for rebind", + nm_device_get_ip_iface (device)); + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); + return; + } + + g_assert (priv->dhcp6_client); /* sanity check */ + + connection = nm_device_get_connection (device); + g_assert (connection); + + /* Apply the updated config */ + if (ip6_config_merge_and_apply (device, TRUE, &reason) == FALSE) { + nm_log_warn (LOGD_DHCP6, "(%s): failed to update IPv6 config in response to DHCP event.", + nm_device_get_ip_iface (device)); + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + } else { + /* Notify dispatcher scripts of new DHCPv6 config */ + nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, device, NULL, NULL, NULL); + } +} + +static void +dhcp6_fail (NMDevice *device, gboolean timeout) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + nm_dhcp6_config_reset (priv->dhcp6_config); + + if (timeout || (priv->ip6_state == IP_CONF)) + nm_device_activate_schedule_ip6_config_timeout (device); + else if (priv->ip6_state == IP_FAIL) + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); +} + +static void +dhcp6_state_changed (NMDHCPClient *client, + NMDHCPState state, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE); + + nm_log_dbg (LOGD_DHCP6, "(%s): new DHCPv6 client state %d", + nm_device_get_iface (device), state); + + switch (state) { + case DHC_BOUND6: + case DHC_RENEW6: /* lease renewed */ + case DHC_REBOOT: /* have valid lease, but now obtained a different one */ + case DHC_REBIND6: /* new, different lease */ + g_clear_object (&priv->dhcp6_ip6_config); + priv->dhcp6_ip6_config = nm_dhcp_client_get_ip6_config (priv->dhcp6_client, FALSE); + + /* Update the DHCP6 config object with new DHCP options */ + nm_dhcp6_config_reset (priv->dhcp6_config); + if (priv->dhcp6_ip6_config) { + nm_dhcp_client_foreach_option (priv->dhcp6_client, + dhcp6_add_option_cb, + priv->dhcp6_config); + } + g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG); + + if (priv->ip6_state == IP_CONF) { + if (priv->dhcp6_ip6_config == NULL) { + /* FIXME: Initial DHCP failed; should we fail IPv6 entirely then? */ + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DHCP_FAILED); + break; + } + nm_device_activate_schedule_ip6_config_result (device); + } else if (priv->ip6_state == IP_DONE) + dhcp6_lease_change (device); + break; + case DHC_TIMEOUT: /* timed out contacting DHCP server */ + dhcp6_fail (device, TRUE); + break; + case DHC_END: /* dhclient exited normally */ + /* In IPv6 info-only mode, the client doesn't handle leases so it + * may exit right after getting a response from the server. That's + * normal. In that case we just ignore the exit. + */ + if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_OTHERCONF) + break; + /* Otherwise, fall through */ + case DHC_FAIL: /* all attempts to contact server timed out, sleeping */ + case DHC_ABEND: /* dhclient exited abnormally */ + /* dhclient quit and can't get/renew a lease; so kill the connection */ + dhcp6_fail (device, FALSE); + break; + default: + break; + } +} + +static void +dhcp6_timeout (NMDHCPClient *client, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + g_return_if_fail (nm_device_get_act_request (device) != NULL); + g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE); + + nm_dhcp_client_stop (client, FALSE); + if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) + dhcp6_fail (device, TRUE); + else { + /* not a hard failure; just live with the RA info */ + nm_dhcp6_config_reset (priv->dhcp6_config); + if (priv->dhcp6_ip6_config) + g_object_unref (priv->dhcp6_ip6_config); + priv->dhcp6_ip6_config = NULL; + + if (priv->ip6_state == IP_CONF) + nm_device_activate_schedule_ip6_config_result (device); + } +} + +static NMActStageReturn +dhcp6_start (NMDevice *self, + NMConnection *connection, + guint32 dhcp_opt, + NMDeviceStateReason *reason) +{ + NMSettingIP6Config *s_ip6; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + GByteArray *tmp = NULL; + + if (!connection) { + connection = nm_device_get_connection (self); + g_assert (connection); + } + + /* Begin a DHCP transaction on the interface */ + + /* Clear old exported DHCP options */ + if (priv->dhcp6_config) + g_object_unref (priv->dhcp6_config); + priv->dhcp6_config = nm_dhcp6_config_new (); + + g_warn_if_fail (priv->dhcp6_ip6_config == NULL); + if (priv->dhcp6_ip6_config) { + g_object_unref (priv->dhcp6_ip6_config); + priv->dhcp6_ip6_config = NULL; + } + + if (priv->hw_addr_len) { + tmp = g_byte_array_sized_new (priv->hw_addr_len); + g_byte_array_append (tmp, priv->hw_addr, priv->hw_addr_len); + } + + priv->dhcp6_client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (), + nm_device_get_ip_iface (self), + tmp, + nm_connection_get_uuid (connection), + nm_device_get_priority (self), + nm_connection_get_setting_ip6_config (connection), + priv->dhcp_timeout, + priv->dhcp_anycast_address, + (dhcp_opt == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE); + if (tmp) + g_byte_array_free (tmp, TRUE); + + if (priv->dhcp6_client) { + priv->dhcp6_state_sigid = g_signal_connect (priv->dhcp6_client, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK (dhcp6_state_changed), + self); + priv->dhcp6_timeout_sigid = g_signal_connect (priv->dhcp6_client, + NM_DHCP_CLIENT_SIGNAL_TIMEOUT, + G_CALLBACK (dhcp6_timeout), + self); + + s_ip6 = nm_connection_get_setting_ip6_config (connection); + if (!nm_setting_ip6_config_get_may_fail (s_ip6) || + !strcmp (nm_setting_ip6_config_get_method (s_ip6), NM_SETTING_IP6_CONFIG_METHOD_DHCP)) + nm_device_add_pending_action (self, PENDING_ACTION_DHCP6, TRUE); + + /* DHCP devices will be notified by the DHCP manager when stuff happens */ + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else { + *reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED; + ret = NM_ACT_STAGE_RETURN_FAILURE; + } + + return ret; +} + +gboolean +nm_device_dhcp6_renew (NMDevice *self, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActStageReturn ret; + NMDeviceStateReason reason; + NMConnection *connection; + + g_return_val_if_fail (priv->dhcp6_client != NULL, FALSE); + + nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 lease renewal requested", + nm_device_get_iface (self)); + + /* Terminate old DHCP instance and release the old lease */ + dhcp6_cleanup (self, TRUE, release); + + connection = nm_device_get_connection (self); + g_assert (connection); + + /* Start DHCP again on the interface */ + ret = dhcp6_start (self, connection, priv->dhcp6_mode, &reason); + + return (ret != NM_ACT_STAGE_RETURN_FAILURE); +} + +/******************************************/ + +static gboolean +linklocal6_config_is_ready (const NMIP6Config *ip6_config) +{ + int i; + + if (!ip6_config) + return FALSE; + + for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) { + const NMPlatformIP6Address *addr = nm_ip6_config_get_address (ip6_config, i); + + if (IN6_IS_ADDR_LINKLOCAL (&addr->address) && + !(addr->flags & IFA_F_TENTATIVE)) + return TRUE; + } + + return FALSE; +} + +static void +linklocal6_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->linklocal6_timeout_id) { + g_source_remove (priv->linklocal6_timeout_id); + priv->linklocal6_timeout_id = 0; + } +} + +static gboolean +linklocal6_timeout_cb (gpointer user_data) +{ + NMDevice *self = user_data; + + linklocal6_cleanup (self); + + nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: waiting for link-local addresses failed due to timeout", + nm_device_get_iface (self)); + + nm_device_activate_schedule_ip6_config_timeout (self); + return G_SOURCE_REMOVE; +} + +static void +linklocal6_complete (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + const char *method; + + g_assert (priv->linklocal6_timeout_id); + g_assert (linklocal6_config_is_ready (priv->ip6_config)); + + linklocal6_cleanup (self); + + connection = nm_device_get_connection (self); + g_assert (connection); + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + + nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: waiting for link-local addresses successful, continue with method %s", + nm_device_get_iface (self), method); + + if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) + addrconf6_start_with_link_ready (self); + else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) + nm_device_activate_schedule_ip6_config_result (self); + else + g_return_if_fail (FALSE); +} + +static NMActStageReturn +linklocal6_start (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + const char *method; + + linklocal6_cleanup (self); + + if (linklocal6_config_is_ready (priv->ip6_config)) + return NM_ACT_STAGE_RETURN_SUCCESS; + + connection = nm_device_get_connection (self); + g_assert (connection); + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.", + nm_device_get_iface (self), method); + + priv->linklocal6_timeout_id = g_timeout_add_seconds (5, linklocal6_timeout_cb, self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/******************************************/ + +static void +print_support_extended_ifa_flags (NMSettingIP6ConfigPrivacy use_tempaddr) +{ + static gint8 warn = 0; + static gint8 s_libnl = -1, s_kernel; + + if (warn >= 2) + return; + + if (s_libnl == -1) { + s_libnl = !!nm_platform_check_support_libnl_extended_ifa_flags (); + s_kernel = !!nm_platform_check_support_kernel_extended_ifa_flags (); + + if (s_libnl && s_kernel) { + nm_log_dbg (LOGD_IP6, "kernel and libnl support extended IFA_FLAGS (needed by NM for IPv6 private addresses)"); + warn = 2; + return; + } + } + + if ( use_tempaddr != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR + && use_tempaddr != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR) { + if (warn == 0) { + nm_log_dbg (LOGD_IP6, "%s%s%s %s not support extended IFA_FLAGS (needed by NM for IPv6 private addresses)", + !s_kernel ? "kernel" : "", + !s_kernel && !s_libnl ? " and " : "", + !s_libnl ? "libnl" : "", + !s_kernel && !s_libnl ? "do" : "does"); + warn = 1; + } + return; + } + + if (!s_libnl && !s_kernel) { + nm_log_warn (LOGD_IP6, "libnl and the kernel do not support extended IFA_FLAGS needed by NM for " + "IPv6 private addresses. This feature is not available"); + } else if (!s_libnl) { + nm_log_warn (LOGD_IP6, "libnl does not support extended IFA_FLAGS needed by NM for " + "IPv6 private addresses. This feature is not available"); + } else if (!s_kernel) { + nm_log_warn (LOGD_IP6, "The kernel does not support extended IFA_FLAGS needed by NM for " + "IPv6 private addresses. This feature is not available"); + } + + warn = 2; +} + +static void +rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMConnection *connection; + int i; + NMDeviceStateReason reason; + static int system_support = -1; + guint ifa_flags = 0x00; + + if (system_support == -1) { + /* + * Check, if both libnl and the kernel are recent enough, + * to help user space handling RA. If it's not supported, + * we have no ipv6-privacy and must add autoconf addresses + * as /128. The reason for the /128 is to prevent the kernel + * from adding a prefix route for this address. + **/ + system_support = nm_platform_check_support_libnl_extended_ifa_flags () && + nm_platform_check_support_kernel_extended_ifa_flags (); + } + + if (system_support) + ifa_flags = IFA_F_NOPREFIXROUTE; + if (priv->rdisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR + || priv->rdisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR) + { + /* without system_support, this flag will be ignored. Still set it, doesn't seem to do any harm. */ + ifa_flags |= IFA_F_MANAGETEMPADDR; + } + + g_return_if_fail (priv->act_request); + connection = nm_device_get_connection (device); + g_assert (connection); + + if (!priv->ac_ip6_config) + priv->ac_ip6_config = nm_ip6_config_new (); + + if (changed & NM_RDISC_CONFIG_GATEWAYS) { + /* Use the first gateway as ordered in router discovery cache. */ + if (rdisc->gateways->len) { + NMRDiscGateway *gateway = &g_array_index (rdisc->gateways, NMRDiscGateway, 0); + + nm_ip6_config_set_gateway (priv->ac_ip6_config, &gateway->address); + } else + nm_ip6_config_set_gateway (priv->ac_ip6_config, NULL); + } + + if (changed & NM_RDISC_CONFIG_ADDRESSES) { + /* Rebuild address list from router discovery cache. */ + nm_ip6_config_reset_addresses (priv->ac_ip6_config); + + /* rdisc->addresses contains at most max_addresses entries. + * This is different from what the kernel does, which + * also counts static and temporary addresses when checking + * max_addresses. + **/ + for (i = 0; i < rdisc->addresses->len; i++) { + NMRDiscAddress *discovered_address = &g_array_index (rdisc->addresses, NMRDiscAddress, i); + NMPlatformIP6Address address; + + memset (&address, 0, sizeof (address)); + address.address = discovered_address->address; + address.plen = system_support ? 64 : 128; + address.timestamp = discovered_address->timestamp; + address.lifetime = discovered_address->lifetime; + address.preferred = discovered_address->preferred; + if (address.preferred > address.lifetime) + address.preferred = address.lifetime; + address.source = NM_PLATFORM_SOURCE_RDISC; + address.flags = ifa_flags; + + nm_ip6_config_add_address (priv->ac_ip6_config, &address); + } + } + + if (changed & NM_RDISC_CONFIG_ROUTES) { + /* Rebuild route list from router discovery cache. */ + nm_ip6_config_reset_routes (priv->ac_ip6_config); + + for (i = 0; i < rdisc->routes->len; i++) { + NMRDiscRoute *discovered_route = &g_array_index (rdisc->routes, NMRDiscRoute, i); + NMPlatformIP6Route route; + + /* Only accept non-default routes. The router has no idea what the + * local configuration or user preferences are, so sending routes + * with a prefix length of 0 is quite rude and thus ignored. + */ + if (discovered_route->plen > 0) { + memset (&route, 0, sizeof (route)); + route.network = discovered_route->network; + route.plen = discovered_route->plen; + route.gateway = discovered_route->gateway; + route.source = NM_PLATFORM_SOURCE_RDISC; + route.metric = nm_device_get_priority (device); + + nm_ip6_config_add_route (priv->ac_ip6_config, &route); + } + } + } + + if (changed & NM_RDISC_CONFIG_DNS_SERVERS) { + /* Rebuild DNS server list from router discovery cache. */ + nm_ip6_config_reset_nameservers (priv->ac_ip6_config); + + for (i = 0; i < rdisc->dns_servers->len; i++) { + NMRDiscDNSServer *discovered_server = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i); + + nm_ip6_config_add_nameserver (priv->ac_ip6_config, &discovered_server->address); + } + } + + if (changed & NM_RDISC_CONFIG_DNS_DOMAINS) { + /* Rebuild domain list from router discovery cache. */ + nm_ip6_config_reset_domains (priv->ac_ip6_config); + + for (i = 0; i < rdisc->dns_domains->len; i++) { + NMRDiscDNSDomain *discovered_domain = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); + + nm_ip6_config_add_domain (priv->ac_ip6_config, discovered_domain->domain); + } + } + + if (changed & NM_RDISC_CONFIG_DHCP_LEVEL) { + dhcp6_cleanup (device, TRUE, TRUE); + + priv->dhcp6_mode = rdisc->dhcp_level; + + switch (priv->dhcp6_mode) { + case NM_RDISC_DHCP_LEVEL_NONE: + break; + default: + nm_log_info (LOGD_DEVICE | LOGD_DHCP6, + "Activation (%s) Stage 3 of 5 (IP Configure Start) starting DHCPv6" + " as requested by IPv6 router...", + priv->iface); + switch (dhcp6_start (device, connection, priv->dhcp6_mode, &reason)) { + case NM_ACT_STAGE_RETURN_SUCCESS: + g_warn_if_reached (); + break; + case NM_ACT_STAGE_RETURN_POSTPONE: + return; + default: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + return; + } + } + } + + if (changed & NM_RDISC_CONFIG_HOP_LIMIT) { + char val[16]; + + g_snprintf (val, sizeof (val), "%d", rdisc->hop_limit); + nm_device_ipv6_sysctl_set (device, "hop_limit", val); + } + + nm_device_activate_schedule_ip6_config_result (device); +} + +static gboolean +addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + NMActStageReturn ret; + const char *ip_iface = nm_device_get_ip_iface (self); + + connection = nm_device_get_connection (self); + g_assert (connection); + + g_warn_if_fail (priv->ac_ip6_config == NULL); + if (priv->ac_ip6_config) { + g_object_unref (priv->ac_ip6_config); + priv->ac_ip6_config = NULL; + } + + priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self), ip_iface); + if (!priv->rdisc) { + nm_log_err (LOGD_IP6, "(%s): failed to start router discovery.", ip_iface); + return FALSE; + } + + priv->rdisc_use_tempaddr = use_tempaddr; + print_support_extended_ifa_flags (use_tempaddr); + + if (!nm_setting_ip6_config_get_may_fail (nm_connection_get_setting_ip6_config (connection))) + nm_device_add_pending_action (self, PENDING_ACTION_AUTOCONF6, TRUE); + + /* ensure link local is ready... */ + ret = linklocal6_start (self); + if (ret == NM_ACT_STAGE_RETURN_SUCCESS) + addrconf6_start_with_link_ready (self); + else + g_return_val_if_fail (ret == NM_ACT_STAGE_RETURN_POSTPONE, TRUE); + + return TRUE; +} + +static void +addrconf6_start_with_link_ready (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_assert (priv->rdisc); + + /* FIXME: what if interface has no lladdr, like PPP? */ + if (priv->hw_addr_len) + nm_rdisc_set_lladdr (priv->rdisc, (const char *) priv->hw_addr, priv->hw_addr_len); + + nm_device_ipv6_sysctl_set (self, "accept_ra", "1"); + nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0"); + nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0"); + nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0"); + + priv->rdisc_config_changed_sigid = g_signal_connect (priv->rdisc, NM_RDISC_CONFIG_CHANGED, + G_CALLBACK (rdisc_config_changed), self); + + nm_rdisc_start (priv->rdisc); +} + +static void +addrconf6_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->rdisc_config_changed_sigid) { + g_signal_handler_disconnect (priv->rdisc, + priv->rdisc_config_changed_sigid); + priv->rdisc_config_changed_sigid = 0; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_AUTOCONF6, FALSE); + + g_clear_object (&priv->ac_ip6_config); + g_clear_object (&priv->rdisc); +} + +/******************************************/ + +static const char *ip6_properties_to_save[] = { + "accept_ra", + "accept_ra_defrtr", + "accept_ra_pinfo", + "accept_ra_rtr_pref", + "disable_ipv6", + "hop_limit", + "use_tempaddr", +}; + +static void +save_ip6_properties (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *ifname = nm_device_get_ip_iface (self); + char *value; + int i; + + g_hash_table_remove_all (priv->ip6_saved_properties); + + for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) { + value = nm_platform_sysctl_get (nm_utils_ip6_property_path (ifname, ip6_properties_to_save[i])); + if (value) { + g_hash_table_insert (priv->ip6_saved_properties, + (char *) ip6_properties_to_save[i], + value); + } + } +} + +static void +restore_ip6_properties (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, priv->ip6_saved_properties); + while (g_hash_table_iter_next (&iter, &key, &value)) + nm_device_ipv6_sysctl_set (self, key, value); +} + +static NMSettingIP6ConfigPrivacy +use_tempaddr_clamp (NMSettingIP6ConfigPrivacy use_tempaddr) +{ + switch (use_tempaddr) { + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + return use_tempaddr; + default: + return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + } +} + +/* Get net.ipv6.conf.default.use_tempaddr value from /etc/sysctl.conf or + * /lib/sysctl.d/sysctl.conf + */ +static NMSettingIP6ConfigPrivacy +ip6_use_tempaddr (void) +{ + char *contents = NULL; + const char *group_name = "[forged_group]\n"; + char *sysctl_data = NULL; + GKeyFile *keyfile; + GError *error = NULL; + gint tmp; + NMSettingIP6ConfigPrivacy ret = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + + /* Read file contents to a string. */ + if (!g_file_get_contents ("/etc/sysctl.conf", &contents, NULL, NULL)) + if (!g_file_get_contents ("/lib/sysctl.d/sysctl.conf", &contents, NULL, NULL)) + return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + + /* Prepend a group so that we can use GKeyFile parser. */ + sysctl_data = g_strdup_printf ("%s%s", group_name, contents); + + keyfile = g_key_file_new (); + if (!g_key_file_load_from_data (keyfile, sysctl_data, -1, G_KEY_FILE_NONE, NULL)) + goto done; + + tmp = g_key_file_get_integer (keyfile, "forged_group", "net.ipv6.conf.default.use_tempaddr", &error); + if (error == NULL) + ret = use_tempaddr_clamp (tmp); + +done: + g_free (contents); + g_free (sysctl_data); + g_clear_error (&error); + g_key_file_free (keyfile); + + return ret; +} + +static gboolean +ip6_requires_slaves (NMConnection *connection) +{ + const char *method; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + + /* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves) + * to complete addressing. SLAAC and DHCP obviously need a peer to + * provide a prefix, while Link-Local must perform DAD on the local link. + */ + return strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 + || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0 + || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0; +} + +static NMActStageReturn +act_stage3_ip6_config_start (NMDevice *self, + NMIP6Config **out_config, + NMDeviceStateReason *reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *ip_iface; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + NMConnection *connection; + const char *method; + NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + const char *ip6_privacy_str = "0\n"; + GSList *slaves; + gboolean ready_slaves; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + ip_iface = nm_device_get_ip_iface (self); + + connection = nm_device_get_connection (self); + g_assert (connection); + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + if (priv->master) + g_assert_cmpstr (method, ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + + if ( strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) != 0 + && priv->is_master + && !priv->carrier) { + nm_log_info (LOGD_IP6 | LOGD_DEVICE, + "(%s): IPv6 config waiting until carrier is on", ip_iface); + return NM_ACT_STAGE_RETURN_WAIT; + } + + if (priv->is_master && ip6_requires_slaves (connection)) { + /* If the master has no ready slaves, and depends on slaves for + * a successful IPv6 attempt, then postpone IPv6 addressing. + */ + slaves = nm_device_master_get_slaves (self); + ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves); + g_slist_free (slaves); + + if (ready_slaves == FALSE) { + nm_log_info (LOGD_DEVICE | LOGD_IP6, + "(%s): IPv6 config waiting until slaves are ready", + ip_iface); + return NM_ACT_STAGE_RETURN_WAIT; + } + } + + priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE; + + if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) { + if (!priv->master) + restore_ip6_properties (self); + return NM_ACT_STAGE_RETURN_STOP; + } + + /* Re-enable IPv6 on the interface */ + nm_device_ipv6_sysctl_set (self, "disable_ipv6", "0"); + + /* Enable/disable IPv6 Privacy Extensions. + * If a global value is configured by sysadmin (e.g. /etc/sysctl.conf), + * use that value instead of per-connection value. + */ + ip6_privacy = ip6_use_tempaddr (); + if (ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) { + NMSettingIP6Config *s_ip6 = nm_connection_get_setting_ip6_config (connection); + + if (s_ip6) + ip6_privacy = nm_setting_ip6_config_get_ip6_privacy (s_ip6); + } + ip6_privacy = use_tempaddr_clamp (ip6_privacy); + + if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) { + if (!addrconf6_start (self, ip6_privacy)) { + /* IPv6 might be disabled; allow IPv4 to proceed */ + ret = NM_ACT_STAGE_RETURN_STOP; + } else + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) { + ret = linklocal6_start (self); + if (ret == NM_ACT_STAGE_RETURN_SUCCESS) { + /* New blank config; LL address is already in priv->ext_ip6_config */ + *out_config = nm_ip6_config_new (); + g_assert (*out_config); + } + } else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) { + priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_MANAGED; + ret = dhcp6_start (self, connection, priv->dhcp6_mode, reason); + } else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) == 0) { + /* New blank config */ + *out_config = nm_ip6_config_new (); + g_assert (*out_config); + + ret = NM_ACT_STAGE_RETURN_SUCCESS; + } else { + nm_log_warn (LOGD_IP6, "(%s): unhandled IPv6 config method '%s'; will fail", + nm_device_get_ip_iface (self), method); + } + + /* Other methods (shared) aren't implemented yet */ + + switch (ip6_privacy) { + case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN: + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + ip6_privacy_str = "0"; + break; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + ip6_privacy_str = "1"; + break; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + ip6_privacy_str = "2"; + break; + } + nm_device_ipv6_sysctl_set (self, "use_tempaddr", ip6_privacy_str); + + return ret; +} + +/** + * nm_device_activate_stage3_ip4_start: + * @self: the device + * + * Try starting IPv4 configuration. + */ +gboolean +nm_device_activate_stage3_ip4_start (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActStageReturn ret; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + NMIP4Config *ip4_config = NULL; + + g_assert (priv->ip4_state == IP_WAIT); + + priv->ip4_state = IP_CONF; + ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &reason); + if (ret == NM_ACT_STAGE_RETURN_SUCCESS) { + g_assert (ip4_config); + nm_device_activate_schedule_ip4_config_result (self, ip4_config); + g_object_unref (ip4_config); + } else if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + return FALSE; + } else if (ret == NM_ACT_STAGE_RETURN_STOP) { + /* Early finish */ + priv->ip4_state = IP_FAIL; + } else if (ret == NM_ACT_STAGE_RETURN_WAIT) { + /* Wait for something to try IP config again */ + priv->ip4_state = IP_WAIT; + } else + g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE); + + return TRUE; +} + +/** + * nm_device_activate_stage3_ip6_start: + * @self: the device + * + * Try starting IPv6 configuration. + */ +gboolean +nm_device_activate_stage3_ip6_start (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActStageReturn ret; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + NMIP6Config *ip6_config = NULL; + + g_assert (priv->ip6_state == IP_WAIT); + + priv->ip6_state = IP_CONF; + ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &reason); + if (ret == NM_ACT_STAGE_RETURN_SUCCESS) { + g_assert (ip6_config); + /* Here we get a static IPv6 config, like for Shared where it's + * autogenerated or from modems where it comes from ModemManager. + */ + g_warn_if_fail (priv->ac_ip6_config == NULL); + priv->ac_ip6_config = ip6_config; + nm_device_activate_schedule_ip6_config_result (self); + } else if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + return FALSE; + } else if (ret == NM_ACT_STAGE_RETURN_STOP) { + /* Early finish */ + priv->ip6_state = IP_FAIL; + } else if (ret == NM_ACT_STAGE_RETURN_WAIT) { + /* Wait for something to try IP config again */ + priv->ip6_state = IP_WAIT; + } else + g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE); + + return TRUE; +} + +/* + * nm_device_activate_stage3_ip_config_start + * + * Begin automatic/manual IP configuration + * + */ +static gboolean +nm_device_activate_stage3_ip_config_start (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface; + NMActiveConnection *master; + NMDevice *master_device; + + /* Clear the activation source ID now that this stage has run */ + activation_source_clear (self, FALSE, 0); + + priv->ip4_state = priv->ip6_state = IP_WAIT; + + iface = nm_device_get_iface (self); + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) started...", iface); + nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE); + + /* Device should be up before we can do anything with it */ + if (!nm_platform_link_is_up (nm_device_get_ip_ifindex (self))) { + nm_log_warn (LOGD_DEVICE, "(%s): interface %s not up for IP configuration", + iface, nm_device_get_ip_iface (self)); + } + + /* If the device is a slave, then we don't do any IP configuration but we + * use the IP config stage to indicate to the master we're ready for + * enslavement. If the master is already activating, it will have tried to + * enslave us when we changed state to IP_CONFIG, causing us to queue a + * transition to SECONDARIES (or FAILED if the enslavement failed), with + * our IP states set to IP_DONE either way. If the master isn't yet + * activating, then they'll still be in IP_WAIT. Either way, we bail out + * of IP config here. + */ + master = nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request)); + if (master) { + master_device = nm_active_connection_get_device (master); + if (priv->ip4_state == IP_WAIT && priv->ip6_state == IP_WAIT) { + nm_log_info (LOGD_DEVICE, "Activation (%s) connection '%s' waiting on master '%s'", + nm_device_get_iface (self), + nm_connection_get_id (nm_device_get_connection (self)), + master_device ? nm_device_get_iface (master_device) : "(unknown)"); + } + goto out; + } + + /* IPv4 */ + if (!nm_device_activate_stage3_ip4_start (self)) + goto out; + + /* IPv6 */ + if (!nm_device_activate_stage3_ip6_start (self)) + goto out; + + if (priv->ip4_state == IP_FAIL && priv->ip6_state == IP_FAIL) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + +out: + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) complete.", iface); + return FALSE; +} + + +static void +fw_change_zone_cb (GError *error, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->fw_call = NULL; + + if (error) { + /* FIXME: fail the device activation? */ + } + + activation_source_schedule (self, nm_device_activate_stage3_ip_config_start, 0); + + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) scheduled.", + nm_device_get_iface (self)); +} + +/* + * nm_device_activate_schedule_stage3_ip_config_start + * + * Schedule IP configuration start + */ +void +nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self) +{ + NMDevicePrivate *priv; + NMConnection *connection; + NMSettingConnection *s_con = NULL; + const char *zone; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + g_return_if_fail (priv->act_request); + + /* Add the interface to the specified firewall zone */ + connection = nm_device_get_connection (self); + g_assert (connection); + s_con = nm_connection_get_setting_connection (connection); + + zone = nm_setting_connection_get_zone (s_con); + nm_log_dbg (LOGD_DEVICE, "Activation (%s) setting firewall zone '%s'", + nm_device_get_iface (self), zone ? zone : "default"); + priv->fw_call = nm_firewall_manager_add_or_change_zone (priv->fw_manager, + nm_device_get_ip_iface (self), + zone, + FALSE, + fw_change_zone_cb, + self); +} + +static NMActStageReturn +act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *reason) +{ + if (nm_device_ip_config_should_fail (self, FALSE)) { + *reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE; + return NM_ACT_STAGE_RETURN_FAILURE; + } + return NM_ACT_STAGE_RETURN_SUCCESS; +} + + +/* + * nm_device_activate_stage4_ip4_config_timeout + * + * Time out on retrieving the IPv4 config. + * + */ +static gboolean +nm_device_activate_ip4_config_timeout (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + /* Clear the activation source ID now that this stage has run */ + activation_source_clear (self, FALSE, AF_INET); + + iface = nm_device_get_iface (self); + nm_log_info (LOGD_DEVICE | LOGD_IP4, + "Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) started...", + iface); + + ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip4_config_timeout (self, &reason); + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) + goto out; + else if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + goto out; + } + g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS); + + priv->ip4_state = IP_FAIL; + + /* If IPv4 failed and IPv6 failed, the activation fails */ + if (priv->ip6_state == IP_FAIL) + nm_device_state_changed (self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + +out: + nm_log_info (LOGD_DEVICE | LOGD_IP4, + "Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) complete.", + iface); + return FALSE; +} + + +/* + * nm_device_activate_schedule_ip4_config_timeout + * + * Deal with a timeout of the IPv4 configuration + * + */ +void +nm_device_activate_schedule_ip4_config_timeout (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + g_return_if_fail (priv->act_request); + + activation_source_schedule (self, nm_device_activate_ip4_config_timeout, AF_INET); + + nm_log_info (LOGD_DEVICE | LOGD_IP4, + "Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) scheduled...", + nm_device_get_iface (self)); +} + + +static NMActStageReturn +act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason) +{ + if (nm_device_ip_config_should_fail (self, TRUE)) { + *reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + + +/* + * nm_device_activate_ip6_config_timeout + * + * Time out on retrieving the IPv6 config. + * + */ +static gboolean +nm_device_activate_ip6_config_timeout (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + /* Clear the activation source ID now that this stage has run */ + activation_source_clear (self, FALSE, AF_INET6); + + iface = nm_device_get_iface (self); + nm_log_info (LOGD_DEVICE | LOGD_IP6, + "Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) started...", + iface); + + ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip6_config_timeout (self, &reason); + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) + goto out; + else if (ret == NM_ACT_STAGE_RETURN_FAILURE) { + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + goto out; + } + g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS); + + priv->ip6_state = IP_FAIL; + + /* If IPv6 failed and IPv4 failed, the activation fails */ + if (priv->ip4_state == IP_FAIL) + nm_device_state_changed (self, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + +out: + nm_log_info (LOGD_DEVICE | LOGD_IP6, + "Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) complete.", + iface); + return FALSE; +} + + +/* + * nm_device_activate_schedule_ip6_config_timeout + * + * Deal with a timeout of the IPv6 configuration + * + */ +void +nm_device_activate_schedule_ip6_config_timeout (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + g_return_if_fail (priv->act_request); + + activation_source_schedule (self, nm_device_activate_ip6_config_timeout, AF_INET6); + + nm_log_info (LOGD_DEVICE | LOGD_IP6, + "Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) scheduled...", + nm_device_get_iface (self)); +} + +static void +share_child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process at this point */ + pid_t pid = getpid (); + setpgid (pid, pid); + + nm_unblock_posix_signals (NULL); +} + +static gboolean +share_init (void) +{ + int status; + char *modules[] = { "ip_tables", "iptable_nat", "nf_nat_ftp", "nf_nat_irc", + "nf_nat_sip", "nf_nat_tftp", "nf_nat_pptp", "nf_nat_h323", + NULL }; + char **iter; + + if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_forward", "1")) { + nm_log_err (LOGD_SHARING, "Error starting IP forwarding: (%d) %s", + errno, strerror (errno)); + return FALSE; + } + + if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_dynaddr", "1")) { + nm_log_err (LOGD_SHARING, "error starting IP forwarding: (%d) %s", + errno, strerror (errno)); + } + + for (iter = modules; *iter; iter++) { + char *argv[3] = { "/sbin/modprobe", *iter, NULL }; + char *envp[1] = { NULL }; + GError *error = NULL; + + if (!g_spawn_sync ("/", argv, envp, G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + share_child_setup, NULL, NULL, NULL, &status, &error)) { + nm_log_err (LOGD_SHARING, "error loading NAT module %s: (%d) %s", + *iter, error ? error->code : 0, + (error && error->message) ? error->message : "unknown"); + if (error) + g_error_free (error); + } + } + + return TRUE; +} + +static void +add_share_rule (NMActRequest *req, const char *table, const char *fmt, ...) +{ + va_list args; + char *cmd; + + va_start (args, fmt); + cmd = g_strdup_vprintf (fmt, args); + va_end (args); + + nm_act_request_add_share_rule (req, table, cmd); + g_free (cmd); +} + +static gboolean +start_sharing (NMDevice *self, NMIP4Config *config) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActRequest *req; + GError *error = NULL; + char str_addr[INET_ADDRSTRLEN + 1]; + char str_mask[INET_ADDRSTRLEN + 1]; + guint32 netmask, network; + const NMPlatformIP4Address *ip4_addr; + const char *ip_iface; + + g_return_val_if_fail (config != NULL, FALSE); + + ip_iface = nm_device_get_ip_iface (self); + + ip4_addr = nm_ip4_config_get_address (config, 0); + if (!ip4_addr || !ip4_addr->address) + return FALSE; + + netmask = nm_utils_ip4_prefix_to_netmask (ip4_addr->plen); + if (!inet_ntop (AF_INET, &netmask, str_mask, sizeof (str_mask))) + return FALSE; + + network = ip4_addr->address & netmask; + if (!inet_ntop (AF_INET, &network, str_addr, sizeof (str_addr))) + return FALSE; + + if (!share_init ()) + return FALSE; + + req = nm_device_get_act_request (self); + g_assert (req); + + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 53 --jump ACCEPT", ip_iface); + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 53 --jump ACCEPT", ip_iface); + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 67 --jump ACCEPT", ip_iface); + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 67 --jump ACCEPT", ip_iface); + add_share_rule (req, "filter", "FORWARD --in-interface %s --jump REJECT", ip_iface); + add_share_rule (req, "filter", "FORWARD --out-interface %s --jump REJECT", ip_iface); + add_share_rule (req, "filter", "FORWARD --in-interface %s --out-interface %s --jump ACCEPT", ip_iface, ip_iface); + add_share_rule (req, "filter", "FORWARD --source %s/%s --in-interface %s --jump ACCEPT", str_addr, str_mask, ip_iface); + add_share_rule (req, "filter", "FORWARD --destination %s/%s --out-interface %s --match state --state ESTABLISHED,RELATED --jump ACCEPT", str_addr, str_mask, ip_iface); + add_share_rule (req, "nat", "POSTROUTING --source %s/%s ! --destination %s/%s --jump MASQUERADE", str_addr, str_mask, str_addr, str_mask); + + nm_act_request_set_shared (req, TRUE); + + if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, config, &error)) { + nm_log_err (LOGD_SHARING, "(%s/%s): failed to start dnsmasq: %s", + nm_device_get_iface (self), ip_iface, + (error && error->message) ? error->message : "(unknown)"); + g_error_free (error); + nm_act_request_set_shared (req, FALSE); + return FALSE; + } + + priv->dnsmasq_state_id = g_signal_connect (priv->dnsmasq_manager, "state-changed", + G_CALLBACK (dnsmasq_state_changed_cb), + self); + return TRUE; +} + +static void +send_arps (NMDevice *self, const char *mode_arg) +{ + const char *argv[] = { "/sbin/arping", mode_arg, "-q", "-I", nm_device_get_ip_iface (self), "-c", "1", NULL, NULL }; + int ip_arg = G_N_ELEMENTS (argv) - 2; + NMConnection *connection; + NMSettingIP4Config *s_ip4; + int i, num; + NMIP4Address *addr; + guint32 ipaddr; + GError *error = NULL; + + connection = nm_device_get_connection (self); + if (!connection) + return; + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (!s_ip4) + return; + num = nm_setting_ip4_config_get_num_addresses (s_ip4); + + for (i = 0; i < num; i++) { + addr = nm_setting_ip4_config_get_address (s_ip4, i); + ipaddr = nm_ip4_address_get_address (addr); + argv[ip_arg] = (char *) nm_utils_inet4_ntop (ipaddr, NULL); + + nm_log_dbg (LOGD_DEVICE | LOGD_IP4, + "Running arping %s -I %s %s", + mode_arg, nm_device_get_iface (self), argv[ip_arg]); + g_spawn_async (NULL, (char **) argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + nm_unblock_posix_signals, + NULL, NULL, &error); + if (error) { + nm_log_warn (LOGD_DEVICE | LOGD_IP4, + "Could not send ARP for local address %s: %s", + argv[ip_arg], error->message); + g_clear_error (&error); + } + } +} + +static gboolean +arp_announce_round2 (gpointer self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->arp_round2_id = 0; + + if ( priv->state >= NM_DEVICE_STATE_IP_CONFIG + && priv->state <= NM_DEVICE_STATE_ACTIVATED) + send_arps (self, "-U"); + + return G_SOURCE_REMOVE; +} + +static void +arp_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->arp_round2_id) { + g_source_remove (priv->arp_round2_id); + priv->arp_round2_id = 0; + } +} + +static void +arp_announce (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + NMSettingIP4Config *s_ip4; + int num; + + arp_cleanup (self); + + /* We only care about manually-configured addresses; DHCP- and autoip-configured + * ones should already have been seen on the network at this point. + */ + connection = nm_device_get_connection (self); + if (!connection) + return; + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (!s_ip4) + return; + num = nm_setting_ip4_config_get_num_addresses (s_ip4); + if (num == 0) + return; + + send_arps (self, "-A"); + priv->arp_round2_id = g_timeout_add_seconds (2, arp_announce_round2, self); +} + +static gboolean +nm_device_activate_ip4_config_commit (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActRequest *req; + const char *iface, *method; + NMConnection *connection; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + /* Clear the activation source ID now that this stage has run */ + activation_source_clear (self, FALSE, AF_INET); + + iface = nm_device_get_iface (self); + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) started...", + iface); + + req = nm_device_get_act_request (self); + g_assert (req); + connection = nm_act_request_get_connection (req); + g_assert (connection); + + /* Device should be up before we can do anything with it */ + if (!nm_platform_link_is_up (nm_device_get_ip_ifindex (self))) { + nm_log_warn (LOGD_DEVICE, "(%s): interface %s not up for IP configuration", + iface, nm_device_get_ip_iface (self)); + } + + /* NULL to use the existing priv->dev_ip4_config */ + if (!ip4_config_merge_and_apply (self, NULL, TRUE, &reason)) { + nm_log_info (LOGD_DEVICE | LOGD_IP4, + "Activation (%s) Stage 5 of 5 (IPv4 Commit) failed", + iface); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + goto out; + } + + /* Start IPv4 sharing if we need it */ + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + + if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) { + if (!start_sharing (self, priv->ip4_config)) { + nm_log_warn (LOGD_SHARING, "Activation (%s) Stage 5 of 5 (IPv4 Commit) start sharing failed.", iface); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED); + goto out; + } + } + + /* If IPv4 wasn't the first to complete, and DHCP was used, then ensure + * dispatcher scripts get the DHCP lease information. + */ + if ( priv->dhcp4_client + && nm_device_activate_ip4_state_in_conf (self) + && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) { + /* Notify dispatcher scripts of new DHCP4 config */ + nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE, + nm_device_get_connection (self), + self, + NULL, + NULL, + NULL); + } + + arp_announce (self); + + /* Enter the IP_CHECK state if this is the first method to complete */ + priv->ip4_state = IP_DONE; + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE); + + if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG) + nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE); + +out: + nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) complete.", + iface); + + return FALSE; +} + +void +nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *config) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + g_return_if_fail (NM_IS_IP4_CONFIG (config)); + priv = NM_DEVICE_GET_PRIVATE (self); + + g_clear_object (&priv->dev_ip4_config); + priv->dev_ip4_config = g_object_ref (config); + + activation_source_schedule (self, nm_device_activate_ip4_config_commit, AF_INET); + + nm_log_info (LOGD_DEVICE | LOGD_IP4, + "Activation (%s) Stage 5 of 5 (IPv4 Configure Commit) scheduled...", + nm_device_get_iface (self)); +} + +gboolean +nm_device_activate_ip4_state_in_conf (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_CONF; +} + +gboolean +nm_device_activate_ip4_state_in_wait (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT; +} + +static gboolean +nm_device_activate_ip6_config_commit (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + guint level = (priv->ip6_state == IP_DONE) ? LOGL_DEBUG : LOGL_INFO; + NMActRequest *req; + const char *iface; + NMConnection *connection; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + + /* Clear the activation source ID now that this stage has run */ + activation_source_clear (self, FALSE, AF_INET6); + + iface = nm_device_get_iface (self); + nm_log (LOGD_DEVICE, level, "Activation (%s) Stage 5 of 5 (IPv6 Commit) started...", iface); + + req = nm_device_get_act_request (self); + g_assert (req); + connection = nm_act_request_get_connection (req); + g_assert (connection); + + /* Device should be up before we can do anything with it */ + g_warn_if_fail (nm_platform_link_is_up (nm_device_get_ip_ifindex (self))); + + /* Allow setting MTU etc */ + if (NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit) + NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self); + + if (ip6_config_merge_and_apply (self, TRUE, &reason)) { + /* If IPv6 wasn't the first IP to complete, and DHCP was used, + * then ensure dispatcher scripts get the DHCP lease information. + */ + if ( priv->dhcp6_client + && nm_device_activate_ip6_state_in_conf (self) + && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) { + /* Notify dispatcher scripts of new DHCP6 config */ + nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, + nm_device_get_connection (self), + self, + NULL, + NULL, + NULL); + } + + /* Enter the IP_CHECK state if this is the first method to complete */ + priv->ip6_state = IP_DONE; + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE); + nm_device_remove_pending_action (self, PENDING_ACTION_AUTOCONF6, FALSE); + + if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG) + nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE); + } else { + nm_log_warn (LOGD_DEVICE | LOGD_IP6, + "Activation (%s) Stage 5 of 5 (IPv6 Commit) failed", + iface); + nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); + } + + nm_log (LOGD_DEVICE, level, "Activation (%s) Stage 5 of 5 (IPv6 Commit) complete.", iface); + + return FALSE; +} + +void +nm_device_activate_schedule_ip6_config_result (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + guint level = (priv->ip6_state == IP_DONE) ? LOGL_DEBUG : LOGL_INFO; + + g_return_if_fail (NM_IS_DEVICE (self)); + + activation_source_schedule (self, nm_device_activate_ip6_config_commit, AF_INET6); + + nm_log (LOGD_DEVICE | LOGD_IP6, level, + "Activation (%s) Stage 5 of 5 (IPv6 Commit) scheduled...", + nm_device_get_iface (self)); +} + +gboolean +nm_device_activate_ip6_state_in_conf (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_CONF; +} + +gboolean +nm_device_activate_ip6_state_in_wait (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT; +} + +static void +clear_act_request (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (!priv->act_request) + return; + + nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request), FALSE); + + if (priv->master_ready_id) { + g_signal_handler_disconnect (priv->act_request, priv->master_ready_id); + priv->master_ready_id = 0; + } + + g_clear_object (&priv->act_request); + g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION); +} + +static void +dnsmasq_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (!priv->dnsmasq_manager) + return; + + if (priv->dnsmasq_state_id) { + g_signal_handler_disconnect (priv->dnsmasq_manager, priv->dnsmasq_state_id); + priv->dnsmasq_state_id = 0; + } + + nm_dnsmasq_manager_stop (priv->dnsmasq_manager); + g_object_unref (priv->dnsmasq_manager); + priv->dnsmasq_manager = NULL; +} + +static void +_update_ip4_address (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + struct ifreq req; + guint32 new_address; + int fd; + + g_return_if_fail (self != NULL); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_IP4, "couldn't open control socket."); + return; + } + + memset (&req, 0, sizeof (struct ifreq)); + strncpy (req.ifr_name, nm_device_get_ip_iface (self), IFNAMSIZ); + if (ioctl (fd, SIOCGIFADDR, &req) == 0) { + new_address = ((struct sockaddr_in *)(&req.ifr_addr))->sin_addr.s_addr; + if (new_address != priv->ip4_address) + priv->ip4_address = new_address; + } + close (fd); +} + +gboolean +nm_device_get_is_nm_owned (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->is_nm_owned; +} + +void +nm_device_set_nm_owned (NMDevice *device) +{ + g_return_if_fail (NM_IS_DEVICE (device)); + + NM_DEVICE_GET_PRIVATE (device)->is_nm_owned = TRUE; +} + +/* + * delete_on_deactivate_link_delete + * + * Function will be queued with g_idle_add to call + * nm_platform_link_delete for the underlying resources + * of the device. + */ +static gboolean +delete_on_deactivate_link_delete (gpointer user_data) +{ + DeleteOnDeactivateData *data = user_data; + + if (data->device) { + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (data->device); + + g_object_remove_weak_pointer (G_OBJECT (data->device), (void **) &data->device); + priv->delete_on_deactivate_data = NULL; + } + + nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)", + data->ifindex, data->idle_add_id); + nm_platform_link_delete (data->ifindex); + g_free (data); + return FALSE; +} + +static void +delete_on_deactivate_unschedule (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->delete_on_deactivate_data) { + DeleteOnDeactivateData *data = priv->delete_on_deactivate_data; + + priv->delete_on_deactivate_data = NULL; + + g_source_remove (data->idle_add_id); + g_object_remove_weak_pointer (G_OBJECT (self), (void **) &data->device); + nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: cancel cleanup and delete virtual link #%d (id=%u)", + data->ifindex, data->idle_add_id); + g_free (data); + } +} + +static void +delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + DeleteOnDeactivateData *data; + + if (ifindex <= 0) + return; + if (!priv->is_nm_owned) + return; + if (!nm_device_is_software (self)) + return; + if (nm_device_get_state (self) == NM_DEVICE_STATE_UNMANAGED) + return; + if (nm_device_get_state (self) == NM_DEVICE_STATE_UNAVAILABLE) + return; + delete_on_deactivate_unschedule (self); /* always cancel and reschedule */ + + data = g_new (DeleteOnDeactivateData, 1); + g_object_add_weak_pointer (G_OBJECT (self), (void **) &data->device); + data->device = self; + data->ifindex = ifindex; + data->idle_add_id = g_idle_add (delete_on_deactivate_link_delete, data); + priv->delete_on_deactivate_data = data; + + nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: schedule cleanup and delete virtual link #%d for [%s] (id=%u)", + ifindex, nm_device_get_iface (self), data->idle_add_id); +} + +static void +disconnect_cb (NMDevice *device, + DBusGMethodInvocation *context, + GError *error, + gpointer user_data) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GError *local = NULL; + + if (error) { + dbus_g_method_return_error (context, error); + return; + } + + /* Authorized */ + if (priv->state <= NM_DEVICE_STATE_DISCONNECTED) { + local = g_error_new_literal (NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "Device is not active"); + dbus_g_method_return_error (context, local); + g_error_free (local); + } else { + priv->autoconnect = FALSE; + + nm_device_state_changed (device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + dbus_g_method_return (context); + } +} + +static void +impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context) +{ + NMConnection *connection; + GError *error = NULL; + + if (NM_DEVICE_GET_PRIVATE (device)->act_request == NULL) { + error = g_error_new_literal (NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "This device is not active"); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + connection = nm_device_get_connection (device); + g_assert (connection); + + /* Ask the manager to authenticate this request for us */ + g_signal_emit (device, signals[AUTH_REQUEST], 0, + context, + connection, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + disconnect_cb, + NULL); +} + +static void +_device_activate (NMDevice *self, NMActRequest *req) +{ + NMDevicePrivate *priv; + NMConnection *connection; + + g_return_if_fail (NM_IS_DEVICE (self)); + g_return_if_fail (NM_IS_ACT_REQUEST (req)); + + priv = NM_DEVICE_GET_PRIVATE (self); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + nm_log_info (LOGD_DEVICE, "Activation (%s) starting connection '%s'", + nm_device_get_iface (self), + nm_connection_get_id (connection)); + + delete_on_deactivate_unschedule (self); + + /* Move default unmanaged devices to DISCONNECTED state here */ + if (nm_device_get_default_unmanaged (self) && priv->state == NM_DEVICE_STATE_UNMANAGED) { + nm_device_state_changed (self, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + } + + /* note: don't notify D-Bus of the new AC here, but do it later when + * changing state to PREPARE so that the two properties change together. + */ + priv->act_request = g_object_ref (req); + + nm_device_activate_schedule_stage1_device_prepare (self); +} + +void +nm_device_queue_activation (NMDevice *self, NMActRequest *req) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (!priv->act_request) { + /* Just activate immediately */ + _device_activate (self, req); + return; + } + + /* supercede any already-queued request */ + g_clear_object (&priv->queued_act_request); + priv->queued_act_request = g_object_ref (req); + + /* Deactivate existing activation request first */ + nm_log_info (LOGD_DEVICE, "(%s): disconnecting for new activation request.", + nm_device_get_iface (self)); + nm_device_state_changed (self, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_NONE); +} + +/* + * nm_device_is_activating + * + * Return whether or not the device is currently activating itself. + * + */ +gboolean +nm_device_is_activating (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMDeviceState state; + + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + state = nm_device_get_state (device); + if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_SECONDARIES) + return TRUE; + + /* There's a small race between the time when stage 1 is scheduled + * and when the device actually sets STATE_PREPARE when the activation + * handler is actually run. If there's an activation handler scheduled + * we're activating anyway. + */ + return priv->act_source_id ? TRUE : FALSE; +} + +/* IP Configuration stuff */ + +NMDHCP4Config * +nm_device_get_dhcp4_config (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), NULL); + + return NM_DEVICE_GET_PRIVATE (self)->dhcp4_config; +} + +NMIP4Config * +nm_device_get_ip4_config (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), NULL); + + return NM_DEVICE_GET_PRIVATE (self)->ip4_config; +} + + +static gboolean +nm_device_set_ip4_config (NMDevice *self, + NMIP4Config *new_config, + gboolean commit, + NMDeviceStateReason *reason) +{ + NMDevicePrivate *priv; + const char *ip_iface; + NMIP4Config *old_config = NULL; + gboolean has_changes = FALSE; + gboolean success = TRUE; + NMDeviceStateReason reason_local = NM_DEVICE_STATE_REASON_NONE; + int ip_ifindex; + + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE (self); + ip_iface = nm_device_get_ip_iface (self); + ip_ifindex = nm_device_get_ip_ifindex (self); + + old_config = priv->ip4_config; + + /* Always commit to nm-platform to update lifetimes */ + if (commit && new_config) { + success = nm_ip4_config_commit (new_config, ip_ifindex); + if (!success) + reason_local = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + } + + if (new_config) { + if (old_config) { + /* has_changes is set only on relevant changes, because when the configuration changes, + * this causes a re-read and reset. This should only happen for relevant changes */ + nm_ip4_config_replace (old_config, new_config, &has_changes); + if (has_changes) { + nm_log_dbg (LOGD_IP4, "(%s): update IP4Config instance (%s)", + ip_iface, nm_ip4_config_get_dbus_path (old_config)); + } + } else { + has_changes = TRUE; + priv->ip4_config = g_object_ref (new_config); + + if (success && !nm_ip4_config_get_dbus_path (new_config)) { + /* Export over D-Bus */ + nm_ip4_config_export (new_config); + } + + nm_log_dbg (LOGD_IP4, "(%s): set IP4Config instance (%s)", + ip_iface, nm_ip4_config_get_dbus_path (new_config)); + } + } else if (old_config) { + has_changes = TRUE; + priv->ip4_config = NULL; + nm_log_dbg (LOGD_IP4, "(%s): clear IP4Config instance (%s)", + ip_iface, nm_ip4_config_get_dbus_path (old_config)); + /* Device config is invalid if combined config is invalid */ + g_clear_object (&priv->dev_ip4_config); + } + + if (has_changes) { + _update_ip4_address (self); + + if (old_config != priv->ip4_config) + g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_CONFIG); + g_signal_emit (self, signals[IP4_CONFIG_CHANGED], 0, priv->ip4_config, old_config); + + if (old_config != priv->ip4_config && old_config) + g_object_unref (old_config); + + if (nm_device_uses_generated_connection (self)) { + NMConnection *connection = nm_device_get_connection (self); + NMSetting *s_ip4; + + g_object_freeze_notify (G_OBJECT (connection)); + nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + s_ip4 = nm_ip4_config_create_setting (priv->ip4_config); + nm_connection_add_setting (connection, s_ip4); + g_object_thaw_notify (G_OBJECT (connection)); + } + + nm_device_queue_recheck_assume (self); + } + + if (reason) + *reason = reason_local; + + return success; +} + +void +nm_device_set_vpn4_config (NMDevice *device, NMIP4Config *config) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + if (priv->vpn4_config == config) + return; + + g_clear_object (&priv->vpn4_config); + if (config) + priv->vpn4_config = g_object_ref (config); + + /* NULL to use existing configs */ + if (!ip4_config_merge_and_apply (device, NULL, TRUE, NULL)) { + nm_log_warn (LOGD_IP4, "(%s): failed to set VPN routes for device", + nm_device_get_ip_iface (device)); + } +} + +static gboolean +nm_device_set_ip6_config (NMDevice *self, + NMIP6Config *new_config, + gboolean commit, + NMDeviceStateReason *reason) +{ + NMDevicePrivate *priv; + const char *ip_iface; + NMIP6Config *old_config = NULL; + gboolean has_changes = FALSE; + gboolean success = TRUE; + NMDeviceStateReason reason_local = NM_DEVICE_STATE_REASON_NONE; + int ip_ifindex; + + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE (self); + ip_iface = nm_device_get_ip_iface (self); + ip_ifindex = nm_device_get_ip_ifindex (self); + + old_config = priv->ip6_config; + + /* Always commit to nm-platform to update lifetimes */ + if (commit && new_config) { + success = nm_ip6_config_commit (new_config, ip_ifindex); + if (!success) + reason_local = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + } + + if (new_config) { + if (old_config) { + /* has_changes is set only on relevant changes, because when the configuration changes, + * this causes a re-read and reset. This should only happen for relevant changes */ + nm_ip6_config_replace (old_config, new_config, &has_changes); + if (has_changes) { + nm_log_dbg (LOGD_IP6, "(%s): update IP6Config instance (%s)", + ip_iface, nm_ip6_config_get_dbus_path (old_config)); + } + } else { + has_changes = TRUE; + priv->ip6_config = g_object_ref (new_config); + + if (success && !nm_ip6_config_get_dbus_path (new_config)) { + /* Export over D-Bus */ + nm_ip6_config_export (new_config); + } + + nm_log_dbg (LOGD_IP4, "(%s): set IP6Config instance (%s)", + ip_iface, nm_ip6_config_get_dbus_path (new_config)); + } + } else if (old_config) { + has_changes = TRUE; + priv->ip6_config = NULL; + nm_log_dbg (LOGD_IP6, "(%s): clear IP6Config instance (%s)", + ip_iface, nm_ip6_config_get_dbus_path (old_config)); + } + + if (has_changes) { + if (old_config != priv->ip6_config) + g_object_notify (G_OBJECT (self), NM_DEVICE_IP6_CONFIG); + g_signal_emit (self, signals[IP6_CONFIG_CHANGED], 0, priv->ip6_config, old_config); + + if (old_config != priv->ip6_config && old_config) + g_object_unref (old_config); + + if (nm_device_uses_generated_connection (self)) { + NMConnection *connection = nm_device_get_connection (self); + NMSetting *s_ip6; + + g_object_freeze_notify (G_OBJECT (connection)); + nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP6_CONFIG); + s_ip6 = nm_ip6_config_create_setting (priv->ip6_config); + nm_connection_add_setting (connection, s_ip6); + g_object_thaw_notify (G_OBJECT (connection)); + } + + nm_device_queue_recheck_assume (self); + } + + if (reason) + *reason = reason_local; + + return success; +} + +void +nm_device_set_vpn6_config (NMDevice *device, NMIP6Config *config) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + if (priv->vpn6_config == config) + return; + + g_clear_object (&priv->vpn6_config); + if (config) + priv->vpn6_config = g_object_ref (config); + + /* NULL to use existing configs */ + if (!ip6_config_merge_and_apply (device, TRUE, NULL)) { + nm_log_warn (LOGD_IP6, "(%s): failed to set VPN routes for device", + nm_device_get_ip_iface (device)); + } +} + +NMDHCP6Config * +nm_device_get_dhcp6_config (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), NULL); + + return NM_DEVICE_GET_PRIVATE (self)->dhcp6_config; +} + +NMIP6Config * +nm_device_get_ip6_config (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), NULL); + + return NM_DEVICE_GET_PRIVATE (self)->ip6_config; +} + +/****************************************************************/ + +static void +dispatcher_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dispatcher.call_id) { + nm_dispatcher_call_cancel (priv->dispatcher.call_id); + priv->dispatcher.call_id = 0; + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + } +} + +static void +dispatcher_complete_proceed_state (guint call_id, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_return_if_fail (call_id == priv->dispatcher.call_id); + + priv->dispatcher.call_id = 0; + nm_device_queue_state (self, priv->dispatcher.post_state, + priv->dispatcher.post_state_reason); + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; +} + +/****************************************************************/ + +static void +ip_check_pre_up (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dispatcher.call_id != 0) { + g_warn_if_reached (); + dispatcher_cleanup (self); + } + + priv->dispatcher.post_state = NM_DEVICE_STATE_SECONDARIES; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_UP, + nm_device_get_connection (self), + self, + dispatcher_complete_proceed_state, + self, + &priv->dispatcher.call_id)) { + /* Just proceed on errors */ + dispatcher_complete_proceed_state (0, self); + } +} + +static void +ip_check_gw_ping_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->gw_ping.watch) { + g_source_remove (priv->gw_ping.watch); + priv->gw_ping.watch = 0; + } + if (priv->gw_ping.timeout) { + g_source_remove (priv->gw_ping.timeout); + priv->gw_ping.timeout = 0; + } + + if (priv->gw_ping.pid) { + guint count = 20; + int status; + + kill (priv->gw_ping.pid, SIGKILL); + do { + if (waitpid (priv->gw_ping.pid, &status, WNOHANG) != 0) + break; + g_usleep (G_USEC_PER_SEC / 20); + } while (count--); + + priv->gw_ping.pid = 0; + } +} + +static void +ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface; + guint log_domain = priv->gw_ping.log_domain; + + if (!priv->gw_ping.watch) + return; + priv->gw_ping.watch = 0; + priv->gw_ping.pid = 0; + + iface = nm_device_get_iface (self); + + if (WIFEXITED (status)) { + if (WEXITSTATUS (status) == 0) + nm_log_dbg (log_domain, "(%s): gateway ping succeeded", iface); + else { + nm_log_warn (log_domain, "(%s): gateway ping failed with error code %d", + iface, WEXITSTATUS (status)); + } + } else + nm_log_warn (log_domain, "(%s): ping stopped unexpectedly with status %d", iface, status); + + /* We've got connectivity, proceed to pre_up */ + ip_check_gw_ping_cleanup (self); + ip_check_pre_up (self); +} + +static gboolean +ip_check_ping_timeout_cb (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->gw_ping.timeout = 0; + + nm_log_warn (priv->gw_ping.log_domain, "(%s): gateway ping timed out", + nm_device_get_iface (self)); + + ip_check_gw_ping_cleanup (self); + ip_check_pre_up (self); + return FALSE; +} + +static gboolean +spawn_ping (NMDevice *self, + guint log_domain, + const char *binary, + const char *address, + guint timeout) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *args[] = { binary, "-I", nm_device_get_ip_iface (self), "-c", "1", "-w", NULL, address, NULL }; + GError *error = NULL; + char *str_timeout, *cmd; + gboolean success; + + g_return_val_if_fail (priv->gw_ping.watch == 0, FALSE); + g_return_val_if_fail (priv->gw_ping.timeout == 0, FALSE); + + args[6] = str_timeout = g_strdup_printf ("%u", timeout); + + if (nm_logging_enabled (LOGL_DEBUG, log_domain)) { + cmd = g_strjoinv (" ", (gchar **) args); + nm_log_dbg (log_domain, "(%s): running '%s'", + nm_device_get_iface (self), + cmd); + g_free (cmd); + } + + success = g_spawn_async ("/", + (gchar **) args, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + nm_unblock_posix_signals, + NULL, + &priv->gw_ping.pid, + &error); + if (success) { + priv->gw_ping.log_domain = log_domain; + priv->gw_ping.watch = g_child_watch_add (priv->gw_ping.pid, ip_check_ping_watch_cb, self); + priv->gw_ping.timeout = g_timeout_add_seconds (timeout + 1, ip_check_ping_timeout_cb, self); + } else { + nm_log_warn (log_domain, "could not spawn %s: %s", binary, error->message); + g_clear_error (&error); + } + + g_free (str_timeout); + return success; +} + +static void +nm_device_start_ip_check (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + NMSettingConnection *s_con; + guint timeout = 0; + const char *ping_binary = NULL; + char buf[INET6_ADDRSTRLEN] = { 0 }; + guint log_domain = LOGD_IP4; + + /* Shouldn't be any active ping here, since IP_CHECK happens after the + * first IP method completes. Any subsequently completing IP method doesn't + * get checked. + */ + g_assert (!priv->gw_ping.watch); + g_assert (!priv->gw_ping.timeout); + g_assert (!priv->gw_ping.pid); + g_assert (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE); + + connection = nm_device_get_connection (self); + g_assert (connection); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + timeout = nm_setting_connection_get_gateway_ping_timeout (s_con); + + if (timeout) { + if (priv->ip4_state == IP_DONE) { + guint gw = 0; + + ping_binary = "/usr/bin/ping"; + log_domain = LOGD_IP4; + + gw = nm_ip4_config_get_gateway (priv->ip4_config); + if (gw && !inet_ntop (AF_INET, &gw, buf, sizeof (buf))) + buf[0] = '\0'; + } else if (priv->ip6_config && priv->ip6_state == IP_DONE) { + const struct in6_addr *gw = NULL; + + ping_binary = "/usr/bin/ping6"; + log_domain = LOGD_IP6; + + gw = nm_ip6_config_get_gateway (priv->ip6_config); + if (gw && !inet_ntop (AF_INET6, gw, buf, sizeof (buf))) + buf[0] = '\0'; + } + } + + if (buf[0]) + spawn_ping (self, log_domain, ping_binary, buf, timeout); + + /* If no ping was started, just advance to pre_up */ + if (!priv->gw_ping.pid) + ip_check_pre_up (self); +} + +/****************************************************************/ + +static gboolean +carrier_wait_timeout (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + + NM_DEVICE_GET_PRIVATE (self)->carrier_wait_id = 0; + nm_device_remove_pending_action (self, "carrier wait", TRUE); + return G_SOURCE_REMOVE; +} + +static gboolean +nm_device_is_up (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + if (NM_DEVICE_GET_CLASS (self)->is_up) + return NM_DEVICE_GET_CLASS (self)->is_up (self); + + return TRUE; +} + +static gboolean +is_up (NMDevice *device) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + return ifindex > 0 ? nm_platform_link_is_up (ifindex) : TRUE; +} + +gboolean +nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + gboolean device_is_up = FALSE; + + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + nm_log_dbg (LOGD_HW, "(%s): bringing up device.", nm_device_get_iface (self)); + + if (NM_DEVICE_GET_CLASS (self)->bring_up) { + if (!NM_DEVICE_GET_CLASS (self)->bring_up (self, no_firmware)) + return FALSE; + } + + device_is_up = nm_device_is_up (self); + if (block && !device_is_up) { + int ifindex = nm_device_get_ip_ifindex (self); + gint64 wait_until = nm_utils_get_monotonic_timestamp_us () + 10000 /* microseconds */; + + do { + g_usleep (200); + if (!nm_platform_link_refresh (ifindex)) + return FALSE; + device_is_up = nm_device_is_up (self); + } while (!device_is_up && nm_utils_get_monotonic_timestamp_us () < wait_until); + } + + if (!device_is_up) { + if (block) + nm_log_warn (LOGD_HW, "(%s): device not up after timeout!", nm_device_get_iface (self)); + else + nm_log_dbg (LOGD_HW, "(%s): device not up immediately", nm_device_get_iface (self)); + return FALSE; + } + + /* Devices that support carrier detect must be IFF_UP to report carrier + * changes; so after setting the device IFF_UP we must suppress startup + * complete (via a pending action) until either the carrier turns on, or + * a timeout is reached. + */ + if (device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) { + if (priv->carrier_wait_id) { + g_source_remove (priv->carrier_wait_id); + nm_device_remove_pending_action (self, "carrier wait", TRUE); + } + priv->carrier_wait_id = g_timeout_add_seconds (5, carrier_wait_timeout, self); + nm_device_add_pending_action (self, "carrier wait", TRUE); + } + + /* Can only get HW address of some devices when they are up */ + nm_device_update_hw_address (self); + + _update_ip4_address (self); + return TRUE; +} + +static void +check_carrier (NMDevice *device) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + if (!device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) + nm_device_set_carrier (device, nm_platform_link_is_connected (ifindex)); +} + +static gboolean +bring_up (NMDevice *device, gboolean *no_firmware) +{ + int ifindex = nm_device_get_ip_ifindex (device); + gboolean result; + + if (ifindex <= 0) { + if (no_firmware) + *no_firmware = FALSE; + return TRUE; + } + + result = nm_platform_link_set_up (ifindex); + if (no_firmware) + *no_firmware = nm_platform_get_error () == NM_PLATFORM_ERROR_NO_FIRMWARE; + + /* Store carrier immediately. */ + if (result && device_has_capability (device, NM_DEVICE_CAP_CARRIER_DETECT)) + check_carrier (device); + + return result; +} + +void +nm_device_take_down (NMDevice *self, gboolean block) +{ + gboolean device_is_up; + + g_return_if_fail (NM_IS_DEVICE (self)); + + nm_log_dbg (LOGD_HW, "(%s): taking down device.", nm_device_get_iface (self)); + + if (NM_DEVICE_GET_CLASS (self)->take_down) { + if (!NM_DEVICE_GET_CLASS (self)->take_down (self)) + return; + } + + device_is_up = nm_device_is_up (self); + if (block && device_is_up) { + int ifindex = nm_device_get_ip_ifindex (self); + gint64 wait_until = nm_utils_get_monotonic_timestamp_us () + 10000 /* microseconds */; + + do { + g_usleep (200); + if (!nm_platform_link_refresh (ifindex)) + return; + device_is_up = nm_device_is_up (self); + } while (device_is_up && nm_utils_get_monotonic_timestamp_us () < wait_until); + } + + if (device_is_up) { + if (block) + nm_log_warn (LOGD_HW, "(%s): device not down after timeout!", nm_device_get_iface (self)); + else + nm_log_dbg (LOGD_HW, "(%s): device not down immediately", nm_device_get_iface (self)); + } +} + +static gboolean +take_down (NMDevice *device) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + if (ifindex > 0) + return nm_platform_link_set_down (ifindex); + + /* devices without ifindex are always up. */ + nm_log_dbg (LOGD_HW, "(%s): cannot take down device without ifindex", nm_device_get_iface (device)); + return FALSE; +} + +void +nm_device_set_firmware_missing (NMDevice *self, gboolean new_missing) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + if (priv->firmware_missing != new_missing) { + priv->firmware_missing = new_missing; + g_object_notify (G_OBJECT (self), NM_DEVICE_FIRMWARE_MISSING); + } +} + +gboolean +nm_device_get_firmware_missing (NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE (self)->firmware_missing; +} + +static NMIP4Config * +find_ip4_lease_config (NMDevice *device, + NMConnection *connection, + NMIP4Config *ext_ip4_config) +{ + const char *ip_iface = nm_device_get_ip_iface (device); + GSList *leases, *liter; + NMIP4Config *found = NULL; + + g_return_val_if_fail (NM_IS_IP4_CONFIG (ext_ip4_config), NULL); + g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); + + leases = nm_dhcp_manager_get_lease_ip_configs (nm_dhcp_manager_get (), + ip_iface, + nm_connection_get_uuid (connection), + FALSE); + for (liter = leases; liter && !found; liter = liter->next) { + NMIP4Config *lease_config = liter->data; + const NMPlatformIP4Address *address = nm_ip4_config_get_address (lease_config, 0); + guint32 gateway = nm_ip4_config_get_gateway (lease_config); + + g_assert (address); + if (!nm_ip4_config_address_exists (ext_ip4_config, address)) + continue; + if (gateway != nm_ip4_config_get_gateway (ext_ip4_config)) + continue; + found = g_object_ref (lease_config); + } + + g_slist_free_full (leases, g_object_unref); + return found; +} + +static void +capture_lease_config (NMDevice *device, + NMIP4Config *ext_ip4_config, + NMIP4Config **out_ip4_config, + NMIP6Config *ext_ip6_config, + NMIP6Config **out_ip6_config) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + const GSList *connections, *citer; + guint i; + gboolean dhcp_used = FALSE; + + /* Ensure at least one address on the device has a non-infinite lifetime, + * otherwise DHCP cannot possibly be active on the device right now. + */ + if (ext_ip4_config && out_ip4_config) { + for (i = 0; i < nm_ip4_config_get_num_addresses (ext_ip4_config); i++) { + const NMPlatformIP4Address *addr = nm_ip4_config_get_address (ext_ip4_config, i); + + if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) { + dhcp_used = TRUE; + break; + } + } + } else if (ext_ip6_config && out_ip6_config) { + for (i = 0; i < nm_ip6_config_get_num_addresses (ext_ip6_config); i++) { + const NMPlatformIP6Address *addr = nm_ip6_config_get_address (ext_ip6_config, i); + + if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) { + dhcp_used = TRUE; + break; + } + } + } else { + g_return_if_fail ( (ext_ip6_config && out_ip6_config) + || (ext_ip4_config && out_ip4_config)); + } + + if (!dhcp_used) + return; + + connections = nm_connection_provider_get_connections (priv->con_provider); + for (citer = connections; citer; citer = citer->next) { + NMConnection *candidate = citer->data; + const char *method; + + if (!nm_device_check_connection_compatible (device, candidate)) + continue; + + /* IPv4 leases */ + method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP4_CONFIG); + if (out_ip4_config && strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) { + *out_ip4_config = find_ip4_lease_config (device, candidate, ext_ip4_config); + if (*out_ip4_config) + return; + } + + /* IPv6 leases */ + method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP6_CONFIG); + if (out_ip6_config && strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) { + /* FIXME: implement find_ip6_lease_config() */ + } + } +} + +static void +update_ip_config (NMDevice *self, gboolean initial) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + int ifindex; + gboolean linklocal6_just_completed = FALSE; + gboolean capture_resolv_conf; + NMDnsManagerResolvConfMode resolv_conf_mode; + + ifindex = nm_device_get_ip_ifindex (self); + if (!ifindex) + return; + + resolv_conf_mode = nm_dns_manager_get_resolv_conf_mode (nm_dns_manager_get ()); + capture_resolv_conf = initial && (resolv_conf_mode == NM_DNS_MANAGER_RESOLV_CONF_EXPLICIT); + + /* IPv4 */ + g_clear_object (&priv->ext_ip4_config); + priv->ext_ip4_config = nm_ip4_config_capture (ifindex, capture_resolv_conf); + + if (priv->ext_ip4_config) { + if (initial) { + g_clear_object (&priv->dev_ip4_config); + capture_lease_config (self, priv->ext_ip4_config, &priv->dev_ip4_config, NULL, NULL); + } + if (priv->dev_ip4_config) + nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config); + if (priv->vpn4_config) + nm_ip4_config_subtract (priv->ext_ip4_config, priv->vpn4_config); + + ip4_config_merge_and_apply (self, NULL, FALSE, NULL); + } + + /* IPv6 */ + g_clear_object (&priv->ext_ip6_config); + priv->ext_ip6_config = nm_ip6_config_capture (ifindex, capture_resolv_conf, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + if (priv->ext_ip6_config) { + + /* Check this before modifying ext_ip6_config */ + linklocal6_just_completed = priv->linklocal6_timeout_id && + linklocal6_config_is_ready (priv->ext_ip6_config); + + if (priv->ac_ip6_config) + nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config); + if (priv->dhcp6_ip6_config) + nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6_ip6_config); + if (priv->vpn6_config) + nm_ip6_config_subtract (priv->ext_ip6_config, priv->vpn6_config); + + ip6_config_merge_and_apply (self, FALSE, NULL); + } + + if (linklocal6_just_completed) { + /* linklocal6 is ready now, do the state transition... we are also + * invoked as g_idle_add, so no problems with reentrance doing it now. + */ + linklocal6_complete (self); + } +} + +void +nm_device_capture_initial_config (NMDevice *dev) +{ + update_ip_config (dev, TRUE); +} + +static gboolean +queued_ip_config_change (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + /* Wait for any queued state changes */ + if (priv->queued_state.id) + return TRUE; + + priv->queued_ip_config_id = 0; + update_ip_config (self, FALSE); + return FALSE; +} + +static void +device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformSignalChangeType change_type, NMPlatformReason reason, gpointer user_data) +{ + NMDevice *self = user_data; + + if (nm_device_get_ip_ifindex (self) == ifindex) { + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (!priv->queued_ip_config_id) + priv->queued_ip_config_id = g_idle_add (queued_ip_config_change, self); + + nm_log_dbg (LOGD_DEVICE, "(%s): queued IP config change", + nm_device_get_iface (self)); + } +} + +static void +nm_device_queued_ip_config_change_clear (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->queued_ip_config_id) { + nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued IP config change", + nm_device_get_iface (self)); + g_source_remove (priv->queued_ip_config_id); + priv->queued_ip_config_id = 0; + } +} + +/** + * nm_device_get_managed(): + * @device: the #NMDevice + * + * Returns: %TRUE if the device is managed + */ +gboolean +nm_device_get_managed (NMDevice *device) +{ + NMDevicePrivate *priv; + gboolean managed; + + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + priv = NM_DEVICE_GET_PRIVATE (device); + + /* Return the composite of all managed flags. However, if the device + * is a default-unmanaged device, and would be managed except for the + * default-unmanaged flag (eg, only NM_UNMANAGED_DEFAULT is set) then + * the device is managed whenever it's not in the UNMANAGED state. + */ + managed = !(priv->unmanaged_flags & ~NM_UNMANAGED_DEFAULT); + if (managed && (priv->unmanaged_flags & NM_UNMANAGED_DEFAULT)) + managed = (priv->state > NM_DEVICE_STATE_UNMANAGED); + + return managed; +} + +/** + * nm_device_get_unmanaged_flag(): + * @device: the #NMDevice + * + * Returns: %TRUE if the device is unmanaged for @flag. + */ +gboolean +nm_device_get_unmanaged_flag (NMDevice *device, NMUnmanagedFlags flag) +{ + return NM_DEVICE_GET_PRIVATE (device)->unmanaged_flags & flag; +} + +/** + * nm_device_get_default_unmanaged(): + * @device: the #NMDevice + * + * Returns: %TRUE if the device is by default unmanaged + */ +static gboolean +nm_device_get_default_unmanaged (NMDevice *device) +{ + return nm_device_get_unmanaged_flag (device, NM_UNMANAGED_DEFAULT); +} + +void +nm_device_set_unmanaged (NMDevice *device, + NMUnmanagedFlags flag, + gboolean unmanaged, + NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + gboolean was_managed, now_managed; + + g_return_if_fail (NM_IS_DEVICE (device)); + g_return_if_fail (flag <= NM_UNMANAGED_LAST); + + priv = NM_DEVICE_GET_PRIVATE (device); + + was_managed = nm_device_get_managed (device); + if (unmanaged) + priv->unmanaged_flags |= flag; + else + priv->unmanaged_flags &= ~flag; + now_managed = nm_device_get_managed (device); + + if (was_managed != now_managed) { + nm_log_dbg (LOGD_DEVICE, "(%s): now %s", + nm_device_get_iface (device), + unmanaged ? "unmanaged" : "managed"); + + g_object_notify (G_OBJECT (device), NM_DEVICE_MANAGED); + + if (unmanaged) + nm_device_state_changed (device, NM_DEVICE_STATE_UNMANAGED, reason); + else + nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, reason); + } +} + +void +nm_device_set_unmanaged_quitting (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + /* It's OK to block here because we're quitting */ + if (nm_device_is_activating (device) || priv->state == NM_DEVICE_STATE_ACTIVATED) + _set_state_full (device, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_REMOVED, TRUE); + + nm_device_set_unmanaged (device, + NM_UNMANAGED_INTERNAL, + TRUE, + NM_DEVICE_STATE_REASON_REMOVED); +} + +/** + * nm_device_set_initial_unmanaged_flag(): + * @device: the #NMDevice + * @flag: an #NMUnmanagedFlag + * @unmanaged: %TRUE or %FALSE to set or clear @flag + * + * Like nm_device_set_unmanaged() but must be set before the device is exported + * and does not trigger state changes. Should only be used when initializing + * a device. + */ +void +nm_device_set_initial_unmanaged_flag (NMDevice *device, + NMUnmanagedFlags flag, + gboolean unmanaged) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (device)); + g_return_if_fail (flag <= NM_UNMANAGED_LAST); + + priv = NM_DEVICE_GET_PRIVATE (device); + g_return_if_fail (priv->path == NULL); + + if (unmanaged) + priv->unmanaged_flags |= flag; + else + priv->unmanaged_flags &= ~flag; +} + +void +nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout) +{ + g_return_if_fail (NM_IS_DEVICE (device)); + + NM_DEVICE_GET_PRIVATE (device)->dhcp_timeout = timeout; +} + +void +nm_device_set_dhcp_anycast_address (NMDevice *device, guint8 *addr) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (device)); + + priv = NM_DEVICE_GET_PRIVATE (device); + + if (priv->dhcp_anycast_address) { + g_byte_array_free (priv->dhcp_anycast_address, TRUE); + priv->dhcp_anycast_address = NULL; + } + + if (addr) { + priv->dhcp_anycast_address = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (priv->dhcp_anycast_address, addr, ETH_ALEN); + } +} + +/** + * nm_device_connection_is_available(): + * @device: the #NMDevice + * @connection: the #NMConnection to check for availability + * @allow_device_override: set to %TRUE to let the device do specific checks + * + * Check if @connection is available to be activated on @device. Normally this + * only checks if the connection is in @device's AvailableConnections property. + * If @allow_device_override is %TRUE then the device is asked to do specific + * checks that may bypass the AvailableConnections property. + * + * Returns: %TRUE if @connection can be activated on @device + */ +gboolean +nm_device_connection_is_available (NMDevice *device, + NMConnection *connection, + gboolean allow_device_override) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + gboolean available = FALSE; + + if (nm_device_get_default_unmanaged (device) && (priv->state == NM_DEVICE_STATE_UNMANAGED)) { + /* default-unmanaged devices in UNMANAGED state have no available connections + * so we must manually check whether the connection is available here. + */ + if ( nm_device_check_connection_compatible (device, connection) + && NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, NULL)) + return TRUE; + } + + available = !!g_hash_table_lookup (priv->available_connections, connection); + if (!available && allow_device_override) { + /* FIXME: hack for hidden WiFi becuase clients didn't consistently + * set the 'hidden' property to indicate hidden SSID networks. If + * activating but the network isn't available let the device recheck + * availability. + */ + if ( nm_device_check_connection_compatible (device, connection) + && NM_DEVICE_GET_CLASS (device)->check_connection_available_wifi_hidden) + available = NM_DEVICE_GET_CLASS (device)->check_connection_available_wifi_hidden (device, connection); + } + + return available; +} + +static void +_signal_available_connections_changed (NMDevice *device) +{ + g_object_notify (G_OBJECT (device), NM_DEVICE_AVAILABLE_CONNECTIONS); +} + +static void +_clear_available_connections (NMDevice *device, gboolean do_signal) +{ + g_hash_table_remove_all (NM_DEVICE_GET_PRIVATE (device)->available_connections); + if (do_signal == TRUE) + _signal_available_connections_changed (device); +} + +static gboolean +_try_add_available_connection (NMDevice *self, NMConnection *connection) +{ + if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED) + return FALSE; + + if (nm_device_check_connection_compatible (self, connection)) { + if (NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, NULL)) { + g_hash_table_insert (NM_DEVICE_GET_PRIVATE (self)->available_connections, + g_object_ref (connection), + GUINT_TO_POINTER (1)); + return TRUE; + } + } + return FALSE; +} + +static gboolean +_del_available_connection (NMDevice *device, NMConnection *connection) +{ + return g_hash_table_remove (NM_DEVICE_GET_PRIVATE (device)->available_connections, connection); +} + +static gboolean +connection_requires_carrier (NMConnection *connection) +{ + NMSettingIP4Config *s_ip4; + NMSettingIP6Config *s_ip6; + const char *method; + gboolean ip4_carrier_wanted = FALSE, ip6_carrier_wanted = FALSE; + gboolean ip4_used = FALSE, ip6_used = FALSE; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + if ( strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) != 0 + && strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0) { + ip4_carrier_wanted = TRUE; + + /* If IPv4 wants a carrier and cannot fail, the whole connection + * requires a carrier regardless of the IPv6 method. + */ + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (s_ip4 && !nm_setting_ip4_config_get_may_fail (s_ip4)) + return TRUE; + } + ip4_used = (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0); + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + if ( strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) != 0 + && strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0) { + ip6_carrier_wanted = TRUE; + + /* If IPv6 wants a carrier and cannot fail, the whole connection + * requires a carrier regardless of the IPv4 method. + */ + s_ip6 = nm_connection_get_setting_ip6_config (connection); + if (s_ip6 && !nm_setting_ip6_config_get_may_fail (s_ip6)) + return TRUE; + } + ip6_used = (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0); + + /* If an IP version wants a carrier and and the other IP version isn't + * used, the connection requires carrier since it will just fail without one. + */ + if (ip4_carrier_wanted && !ip6_used) + return TRUE; + if (ip6_carrier_wanted && !ip4_used) + return TRUE; + + /* If both want a carrier, the whole connection wants a carrier */ + return ip4_carrier_wanted && ip6_carrier_wanted; +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + /* Connections which require a network connection are not available when + * the device has no carrier, even with ignore-carrer=TRUE. + */ + if (NM_DEVICE_GET_PRIVATE (device)->carrier == FALSE) + return connection_requires_carrier (connection) ? FALSE : TRUE; + + return TRUE; +} + +void +nm_device_recheck_available_connections (NMDevice *device) +{ + NMDevicePrivate *priv; + const GSList *connections, *iter; + + g_return_if_fail (NM_IS_DEVICE (device)); + + priv = NM_DEVICE_GET_PRIVATE(device); + + if (priv->con_provider) { + _clear_available_connections (device, FALSE); + + connections = nm_connection_provider_get_connections (priv->con_provider); + for (iter = connections; iter; iter = g_slist_next (iter)) + _try_add_available_connection (device, NM_CONNECTION (iter->data)); + + _signal_available_connections_changed (device); + } +} + +/** + * nm_device_get_available_connections: + * @device: the #NMDevice + * @specific_object: a specific object path if any + * + * Returns a list of connections available to activate on the device, taking + * into account any device-specific details given by @specific_object (like + * WiFi access point path). + * + * Returns: caller-owned #GPtrArray of #NMConnections + */ +GPtrArray * +nm_device_get_available_connections (NMDevice *device, const char *specific_object) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GHashTableIter iter; + guint num_available; + NMConnection *connection = NULL; + GPtrArray *array = NULL; + + num_available = g_hash_table_size (priv->available_connections); + if (num_available > 0) { + array = g_ptr_array_sized_new (num_available); + g_hash_table_iter_init (&iter, priv->available_connections); + while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) { + /* If a specific object is given, only include connections that are + * compatible with it. + */ + if ( !specific_object + || NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, specific_object)) + g_ptr_array_add (array, connection); + } + } + return array; +} + +static void +cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) +{ + if (_try_add_available_connection (NM_DEVICE (user_data), connection)) + _signal_available_connections_changed (NM_DEVICE (user_data)); +} + +static void +cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) +{ + if (_del_available_connection (NM_DEVICE (user_data), connection)) + _signal_available_connections_changed (NM_DEVICE (user_data)); +} + +static void +cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) +{ + gboolean added, deleted; + + /* FIXME: don't remove it from the hash if it's just going to get re-added */ + deleted = _del_available_connection (NM_DEVICE (user_data), connection); + added = _try_add_available_connection (NM_DEVICE (user_data), connection); + + /* Only signal if the connection was removed OR added, but not both */ + if (added != deleted) + _signal_available_connections_changed (NM_DEVICE (user_data)); +} + +gboolean +nm_device_supports_vlans (NMDevice *device) +{ + return nm_platform_link_supports_vlans (nm_device_get_ifindex (device)); +} + +/** + * nm_device_add_pending_action(): + * @device: the #NMDevice to add the pending action to + * @action: a static string that identifies the action + * @assert_not_yet_pending: if %TRUE, assert that the @action is currently not yet pending. + * Otherwise, ignore duplicate scheduling of the same action silently. + * + * Adds a pending action to the device. + * + * Returns: %TRUE if the action was added (and not already added before). %FALSE + * if the same action is already scheduled. In the latter case, the action was not scheduled + * a second time. + */ +gboolean +nm_device_add_pending_action (NMDevice *device, const char *action, gboolean assert_not_yet_pending) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GSList *iter; + guint count = 0; + + g_return_val_if_fail (action, FALSE); + + /* Check if the action is already pending. Cannot add duplicate actions */ + for (iter = priv->pending_actions; iter; iter = iter->next) { + if (!strcmp (action, iter->data)) { + if (assert_not_yet_pending) { + nm_log_warn (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending", + nm_device_get_iface (device), + count + g_slist_length (iter), + action); + g_return_val_if_reached (FALSE); + } else { + nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending (expected)", + nm_device_get_iface (device), + count + g_slist_length (iter), + action); + } + return FALSE; + } + count++; + } + + priv->pending_actions = g_slist_append (priv->pending_actions, g_strdup (action)); + count++; + + nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s'", + nm_device_get_iface (device), + count, + action); + + if (count == 1) + g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION); + + return TRUE; +} + +/** + * nm_device_remove_pending_action(): + * @device: the #NMDevice to remove the pending action from + * @action: a static string that identifies the action + * @assert_is_pending: if %TRUE, assert that the @action is pending. + * If %FALSE, don't do anything if the current action is not pending and + * return %FALSE. + * + * Removes a pending action previously added by nm_device_add_pending_action(). + * + * Returns: whether the @action was pending and is now removed. + */ +gboolean +nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GSList *iter; + guint count = 0; + + g_return_val_if_fail (action, FALSE); + + for (iter = priv->pending_actions; iter; iter = iter->next) { + if (!strcmp (action, iter->data)) { + nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s'", + nm_device_get_iface (device), + count + g_slist_length (iter->next), /* length excluding 'iter' */ + action); + g_free (iter->data); + priv->pending_actions = g_slist_delete_link (priv->pending_actions, iter); + if (priv->pending_actions == NULL) + g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION); + return TRUE; + } + count++; + } + + if (assert_is_pending) { + nm_log_warn (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending", + nm_device_get_iface (device), + count, + action); + g_return_val_if_reached (FALSE); + } else { + nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending (expected)", + nm_device_get_iface (device), + count, + action); + } + return FALSE; +} + +gboolean +nm_device_has_pending_action (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + return !!priv->pending_actions; +} + +/***********************************************************/ + +static void +_cleanup_generic_pre (NMDevice *self, gboolean deconfigure) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + /* Clean up when device was deactivated during call to firewall */ + if (priv->fw_manager) { + NMConnection *connection; + + if (priv->fw_call) { + nm_firewall_manager_cancel_call (priv->fw_manager, priv->fw_call); + priv->fw_call = NULL; + } + + connection = nm_device_get_connection (self); + if (deconfigure && connection) { + nm_firewall_manager_remove_from_zone (priv->fw_manager, + nm_device_get_ip_iface (self), + NULL); + } + } + + ip_check_gw_ping_cleanup (self); + + /* Break the activation chain */ + activation_source_clear (self, TRUE, AF_INET); + activation_source_clear (self, TRUE, AF_INET6); + + /* Clear any queued transitions */ + nm_device_queued_state_clear (self); + nm_device_queued_ip_config_change_clear (self); + + priv->ip4_state = priv->ip6_state = IP_NONE; + + dhcp4_cleanup (self, deconfigure, FALSE); + arp_cleanup (self); + dhcp6_cleanup (self, deconfigure, FALSE); + linklocal6_cleanup (self); + addrconf6_cleanup (self); + dnsmasq_cleanup (self); + aipd_cleanup (self); +} + +static void +_cleanup_generic_post (NMDevice *self, gboolean deconfigure) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE; + + /* Clean up IP configs; this does not actually deconfigure the + * interface; the caller must flush routes and addresses explicitly. + */ + nm_device_set_ip4_config (self, NULL, TRUE, &ignored); + nm_device_set_ip6_config (self, NULL, TRUE, &ignored); + g_clear_object (&priv->dev_ip4_config); + g_clear_object (&priv->ext_ip4_config); + g_clear_object (&priv->vpn4_config); + g_clear_object (&priv->ip4_config); + g_clear_object (&priv->ac_ip6_config); + g_clear_object (&priv->ext_ip6_config); + g_clear_object (&priv->vpn6_config); + g_clear_object (&priv->ip6_config); + + clear_act_request (self); + + /* Clear legacy IPv4 address property */ + if (priv->ip4_address) { + priv->ip4_address = 0; + g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_ADDRESS); + } + + if (deconfigure) { + /* Check if the device was deactivated, and if so, delete_link. + * Don't call delete_link synchronously because we are currently + * handling a state change -- which is not reentrant. */ + delete_on_deactivate_check_and_schedule (self, nm_device_get_ip_ifindex (self)); + } + + /* ip_iface should be cleared after flushing all routes and addreses, since + * those are identified by ip_iface, not by iface (which might be a tty + * or ATM device). + */ + nm_device_set_ip_iface (self, NULL); +} + +/* + * nm_device_cleanup + * + * Remove a device's routing table entries and IP addresses. + * + */ +static void +nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + int ifindex; + + g_return_if_fail (NM_IS_DEVICE (self)); + + if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED) { + nm_log_info (LOGD_DEVICE, "(%s): preparing device", + nm_device_get_iface (self)); + } else { + nm_log_info (LOGD_DEVICE, "(%s): deactivating device (reason '%s') [%d]", + nm_device_get_iface (self), reason_to_string (reason), reason); + } + + /* Save whether or not we tried IPv6 for later */ + priv = NM_DEVICE_GET_PRIVATE (self); + + _cleanup_generic_pre (self, TRUE); + + /* Turn off kernel IPv6 */ + nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1"); + nm_device_ipv6_sysctl_set (self, "accept_ra", "0"); + nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0"); + + /* Call device type-specific deactivation */ + if (NM_DEVICE_GET_CLASS (self)->deactivate) + NM_DEVICE_GET_CLASS (self)->deactivate (self); + + /* master: release slaves */ + nm_device_master_release_slaves (self); + + /* slave: mark no longer enslaved */ + g_clear_object (&priv->master); + priv->enslaved = FALSE; + g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER); + + /* Take out any entries in the routing table and any IP address the device had. */ + ifindex = nm_device_get_ip_ifindex (self); + if (ifindex > 0) { + nm_platform_route_flush (ifindex); + nm_platform_address_flush (ifindex); + } + + _cleanup_generic_post (self, TRUE); +} + +/***********************************************************/ + +static gboolean +ip_config_valid (NMDeviceState state) +{ + return (state == NM_DEVICE_STATE_UNMANAGED) || + (state >= NM_DEVICE_STATE_IP_CHECK && + state <= NM_DEVICE_STATE_DEACTIVATING); +} + +static void +notify_ip_properties (NMDevice *device) +{ + g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE); + g_object_notify (G_OBJECT (device), NM_DEVICE_IP4_CONFIG); + g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG); + g_object_notify (G_OBJECT (device), NM_DEVICE_IP6_CONFIG); + g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG); +} + +static void +_set_state_full (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason, + gboolean quitting) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMDeviceState old_state; + NMActRequest *req; + gboolean no_firmware = FALSE; + NMConnection *connection; + + /* Track re-entry */ + g_warn_if_fail (priv->in_state_changed == FALSE); + priv->in_state_changed = TRUE; + + g_return_if_fail (NM_IS_DEVICE (device)); + + /* Do nothing if state isn't changing, but as a special case allow + * re-setting UNAVAILABLE if the device is missing firmware so that we + * can retry device initialization. + */ + if ( (priv->state == state) + && !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing)) { + priv->in_state_changed = FALSE; + return; + } + + old_state = priv->state; + priv->state = state; + priv->state_reason = reason; + + nm_log_info (LOGD_DEVICE, "(%s): device state change: %s -> %s (reason '%s') [%d %d %d]", + nm_device_get_iface (device), + state_to_string (old_state), + state_to_string (state), + reason_to_string (reason), + old_state, + state, + reason); + + /* Clear any queued transitions */ + nm_device_queued_state_clear (device); + + dispatcher_cleanup (device); + + /* Cache the activation request for the dispatcher */ + req = priv->act_request ? g_object_ref (priv->act_request) : NULL; + + if (state <= NM_DEVICE_STATE_UNAVAILABLE) { + _clear_available_connections (device, TRUE); + g_clear_object (&priv->queued_act_request); + } + + /* Update the available connections list when a device first becomes available */ + if ( state >= NM_DEVICE_STATE_DISCONNECTED + && old_state < NM_DEVICE_STATE_DISCONNECTED) + nm_device_recheck_available_connections (device); + + /* Handle the new state here; but anything that could trigger + * another state change should be done below. + */ + switch (state) { + case NM_DEVICE_STATE_UNMANAGED: + nm_device_set_firmware_missing (device, FALSE); + if (old_state > NM_DEVICE_STATE_UNMANAGED) { + /* Clean up if the device is now unmanaged but was activated */ + if (nm_device_get_act_request (device)) + nm_device_cleanup (device, reason); + nm_device_take_down (device, TRUE); + restore_ip6_properties (device); + } + break; + case NM_DEVICE_STATE_UNAVAILABLE: + if (old_state == NM_DEVICE_STATE_UNMANAGED) { + save_ip6_properties (device); + if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) { + nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1"); + nm_device_ipv6_sysctl_set (device, "accept_ra_defrtr", "0"); + nm_device_ipv6_sysctl_set (device, "accept_ra_pinfo", "0"); + nm_device_ipv6_sysctl_set (device, "accept_ra_rtr_pref", "0"); + nm_device_ipv6_sysctl_set (device, "use_tempaddr", "0"); + } + } + + if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) { + if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware) + nm_log_warn (LOGD_HW, "(%s): firmware may be missing.", nm_device_get_iface (device)); + nm_device_set_firmware_missing (device, no_firmware ? TRUE : FALSE); + } + /* Ensure the device gets deactivated in response to stuff like + * carrier changes or rfkill. But don't deactivate devices that are + * about to assume a connection since that defeats the purpose of + * assuming the device's existing connection. + * + * Note that we "deactivate" the device even when coming from + * UNMANAGED, to ensure that it's in a clean state. + */ + if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) + nm_device_cleanup (device, reason); + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (old_state > NM_DEVICE_STATE_UNAVAILABLE) + nm_device_cleanup (device, reason); + break; + default: + break; + } + + /* Reset autoconnect flag when the device is activating or connected. */ + if ( state >= NM_DEVICE_STATE_PREPARE + && state <= NM_DEVICE_STATE_ACTIVATED) + priv->autoconnect = TRUE; + + g_object_notify (G_OBJECT (device), NM_DEVICE_STATE); + g_object_notify (G_OBJECT (device), NM_DEVICE_STATE_REASON); + g_signal_emit_by_name (device, "state-changed", state, old_state, reason); + + /* Post-process the event after internal notification */ + + switch (state) { + case NM_DEVICE_STATE_UNAVAILABLE: + /* If the device can activate now (ie, it's got a carrier, the supplicant + * is active, or whatever) schedule a delayed transition to DISCONNECTED + * to get things rolling. The device can't transition immediately because + * we can't change states again from the state handler for a variety of + * reasons. + */ + if (nm_device_is_available (device)) { + nm_log_dbg (LOGD_DEVICE, "(%s): device is available, will transition to DISCONNECTED", + nm_device_get_iface (device)); + nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); + } else { + if (old_state == NM_DEVICE_STATE_UNMANAGED) { + nm_log_dbg (LOGD_DEVICE, "(%s): device not yet available for transition to DISCONNECTED", + nm_device_get_iface (device)); + } else if ( old_state > NM_DEVICE_STATE_UNAVAILABLE + && nm_device_get_default_unmanaged (device)) + nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); + } + break; + case NM_DEVICE_STATE_DEACTIVATING: + if (quitting) { + nm_dispatcher_call_sync (DISPATCHER_ACTION_PRE_DOWN, + nm_act_request_get_connection (req), + device); + } else { + priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED; + priv->dispatcher.post_state_reason = reason; + if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_DOWN, + nm_act_request_get_connection (req), + device, + dispatcher_complete_proceed_state, + device, + &priv->dispatcher.call_id)) { + /* Just proceed on errors */ + dispatcher_complete_proceed_state (0, device); + } + } + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (priv->queued_act_request) { + NMActRequest *queued_req; + + queued_req = priv->queued_act_request; + priv->queued_act_request = NULL; + _device_activate (device, queued_req); + g_object_unref (queued_req); + } else if ( old_state > NM_DEVICE_STATE_DISCONNECTED + && nm_device_get_default_unmanaged (device)) + nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); + break; + case NM_DEVICE_STATE_ACTIVATED: + nm_log_info (LOGD_DEVICE, "Activation (%s) successful, device activated.", + nm_device_get_iface (device)); + nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), device, NULL, NULL, NULL); + break; + case NM_DEVICE_STATE_FAILED: + connection = nm_device_get_connection (device); + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s) failed for connection '%s'", + nm_device_get_iface (device), + connection ? nm_connection_get_id (connection) : "<unknown>"); + + /* Notify any slaves of the unexpected failure */ + nm_device_master_release_slaves (device); + + /* If the connection doesn't yet have a timestamp, set it to zero so that + * we can distinguish between connections we've tried to activate and have + * failed (zero timestamp), connections that succeeded (non-zero timestamp), + * and those we haven't tried yet (no timestamp). + */ + if (connection && !nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), NULL)) { + nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (connection), + (guint64) 0, + TRUE); + } + + /* Schedule the transition to DISCONNECTED. The device can't transition + * immediately because we can't change states again from the state + * handler for a variety of reasons. + */ + nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); + break; + case NM_DEVICE_STATE_IP_CHECK: + nm_device_start_ip_check (device); + + /* IP-related properties are only valid when the device has IP configuration; + * now that it does, ensure their change notifications are emitted. + */ + notify_ip_properties (device); + break; + case NM_DEVICE_STATE_SECONDARIES: + ip_check_gw_ping_cleanup (device); + nm_log_dbg (LOGD_DEVICE, "(%s): device entered SECONDARIES state", + nm_device_get_iface (device)); + break; + default: + break; + } + + if (state > NM_DEVICE_STATE_DISCONNECTED) + delete_on_deactivate_unschedule (device); + + if ( (old_state == NM_DEVICE_STATE_ACTIVATED || old_state == NM_DEVICE_STATE_DEACTIVATING) + && (state != NM_DEVICE_STATE_DEACTIVATING)) { + if (quitting) + nm_dispatcher_call_sync (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device); + else + nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device, NULL, NULL, NULL); + } + + /* IP-related properties are only valid when the device has IP configuration. + * If it no longer does, ensure their change notifications are emitted. + */ + if (ip_config_valid (old_state) && !ip_config_valid (state)) + notify_ip_properties (device); + + /* Dispose of the cached activation request */ + if (req) + g_object_unref (req); + + priv->in_state_changed = FALSE; +} + +void +nm_device_state_changed (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason) +{ + _set_state_full (device, state, reason, FALSE); +} + +static gboolean +queued_set_state (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceState new_state; + NMDeviceStateReason new_reason; + + if (priv->queued_state.id) { + nm_log_dbg (LOGD_DEVICE, "(%s): running queued state change to %s (id %d)", + nm_device_get_iface (self), + state_to_string (priv->queued_state.state), + priv->queued_state.id); + + /* Clear queued state struct before triggering state change, since + * the state change may queue another state. + */ + priv->queued_state.id = 0; + new_state = priv->queued_state.state; + new_reason = priv->queued_state.reason; + nm_device_queued_state_clear (self); + + nm_device_state_changed (self, new_state, new_reason); + nm_device_remove_pending_action (self, queued_state_to_string (new_state), TRUE); + } else { + g_warn_if_fail (priv->queued_state.state == NM_DEVICE_STATE_UNKNOWN); + g_warn_if_fail (priv->queued_state.reason == NM_DEVICE_STATE_REASON_NONE); + } + return FALSE; +} + +void +nm_device_queue_state (NMDevice *self, + NMDeviceState state, + NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->queued_state.id && priv->queued_state.state == state) + return; + + /* Add pending action for the new state before clearing the queued states, so + * that we don't accidently pop all pending states and reach 'startup complete' */ + nm_device_add_pending_action (self, queued_state_to_string (state), TRUE); + + /* We should only ever have one delayed state transition at a time */ + if (priv->queued_state.id) { + nm_log_warn (LOGD_DEVICE, "(%s): overwriting previously queued state change to %s (%s)", + nm_device_get_iface (self), + state_to_string (priv->queued_state.state), + reason_to_string (priv->queued_state.reason)); + nm_device_queued_state_clear (self); + } + + priv->queued_state.state = state; + priv->queued_state.reason = reason; + priv->queued_state.id = g_idle_add (queued_set_state, self); + + nm_log_dbg (LOGD_DEVICE, "(%s): queued state change to %s due to %s (id %d)", + nm_device_get_iface (self), state_to_string (state), reason_to_string (reason), + priv->queued_state.id); +} + +NMDeviceState +nm_device_queued_state_peek (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_STATE_UNKNOWN); + + priv = NM_DEVICE_GET_PRIVATE (self); + + return priv->queued_state.id ? priv->queued_state.state : NM_DEVICE_STATE_UNKNOWN; +} + +void +nm_device_queued_state_clear (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->queued_state.id) { + nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued state transition (id %d)", + nm_device_get_iface (self), priv->queued_state.id); + g_source_remove (priv->queued_state.id); + nm_device_remove_pending_action (self, queued_state_to_string (priv->queued_state.state), TRUE); + } + memset (&priv->queued_state, 0, sizeof (priv->queued_state)); +} + +NMDeviceState +nm_device_get_state (NMDevice *device) +{ + g_return_val_if_fail (NM_IS_DEVICE (device), NM_DEVICE_STATE_UNKNOWN); + + return NM_DEVICE_GET_PRIVATE (device)->state; +} + +/***********************************************************/ +/* NMConfigDevice interface related stuff */ + +static guint +nm_device_get_hw_address_length (NMDevice *dev, gboolean *out_permanent) +{ + return NM_DEVICE_GET_CLASS (dev)->get_hw_address_length (dev, out_permanent); +} + +const guint8 * +nm_device_get_hw_address (NMDevice *dev, guint *out_len) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail (NM_IS_DEVICE (dev), NULL); + priv = NM_DEVICE_GET_PRIVATE (dev); + + if (out_len) + *out_len = priv->hw_addr_len; + + if (priv->hw_addr_len == 0) + return NULL; + else + return priv->hw_addr; +} + +gboolean +nm_device_update_hw_address (NMDevice *dev) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + gboolean changed = FALSE, permanent = FALSE; + + priv->hw_addr_len = nm_device_get_hw_address_length (dev, &permanent); + + /* If the address can't be changed, don't bother trying */ + if (permanent) + return FALSE; + + if (priv->hw_addr_len) { + int ifindex = nm_device_get_ip_ifindex (dev); + gsize addrlen; + const guint8 *binaddr; + + g_return_val_if_fail (ifindex > 0, FALSE); + + binaddr = nm_platform_link_get_address (ifindex, &addrlen); + + if (addrlen != priv->hw_addr_len) { + nm_log_err (LOGD_HW | LOGD_DEVICE, + "(%s): hardware address is wrong length (got %zd, expected %d)", + nm_device_get_iface (dev), addrlen, priv->hw_addr_len); + } else { + changed = !!memcmp (priv->hw_addr, binaddr, addrlen); + if (changed) { + char *addrstr = nm_utils_hwaddr_ntoa_len (binaddr, priv->hw_addr_len); + + memcpy (priv->hw_addr, binaddr, addrlen); + nm_log_dbg (LOGD_HW | LOGD_DEVICE, + "(%s): hardware address is %s", + nm_device_get_iface (dev), addrstr); + g_free (addrstr); + g_object_notify (G_OBJECT (dev), NM_DEVICE_HW_ADDRESS); + } + } + } else { + int i; + + /* hw_addr_len is now 0; see if hw_addr was already empty */ + for (i = 0; i < sizeof (priv->hw_addr) && !changed; i++) { + if (priv->hw_addr[i]) + changed = TRUE; + } + if (changed) { + memset (priv->hw_addr, 0, sizeof (priv->hw_addr)); + nm_log_dbg (LOGD_HW | LOGD_DEVICE, + "(%s): previous hardware address is no longer valid", + nm_device_get_iface (dev)); + g_object_notify (G_OBJECT (dev), NM_DEVICE_HW_ADDRESS); + } + } + + return changed; +} + +gboolean +nm_device_set_hw_addr (NMDevice *device, const guint8 *addr, + const char *detail, guint64 hw_log_domain) +{ + const char *iface; + char *mac_str = NULL; + gboolean success = FALSE; + guint len; + const guint8 *cur_addr = nm_device_get_hw_address (device, &len); + + g_return_val_if_fail (addr != NULL, FALSE); + + iface = nm_device_get_iface (device); + + /* Do nothing if current MAC is same */ + if (cur_addr && !memcmp (cur_addr, addr, len)) { + nm_log_dbg (LOGD_DEVICE | hw_log_domain, "(%s): no MAC address change needed", iface); + return TRUE; + } + + mac_str = nm_utils_hwaddr_ntoa_len (addr, len); + + /* Can't change MAC address while device is up */ + nm_device_take_down (device, FALSE); + + success = nm_platform_link_set_address (nm_device_get_ip_ifindex (device), addr, len); + if (success) { + /* MAC address succesfully changed; update the current MAC to match */ + nm_device_update_hw_address (device); + cur_addr = nm_device_get_hw_address (device, NULL); + if (memcmp (cur_addr, addr, len) == 0) { + nm_log_info (LOGD_DEVICE | hw_log_domain, "(%s): %s MAC address to %s", + iface, detail, mac_str); + } else { + nm_log_warn (LOGD_DEVICE | hw_log_domain, "(%s): new MAC address %s " + "not successfully set", + iface, mac_str); + success = FALSE; + } + } else { + nm_log_warn (LOGD_DEVICE | hw_log_domain, "(%s): failed to %s MAC address to %s", + iface, detail, mac_str); + } + nm_device_bring_up (device, TRUE, NULL); + g_free (mac_str); + + return success; +} + +/** + * nm_device_spec_match_list: + * @device: an #NMDevice + * @specs: (element-type utf8): a list of device specs + * + * Checks if @device matches any of the specifications in @specs. The + * currently-supported spec types are: + * + * "mac:00:11:22:33:44:55" - matches a device with the given + * hardware address + * + * "interface-name:foo0" - matches a device with the given + * interface name + * + * "s390-subchannels:00.11.22" - matches a device with the given + * z/VM / s390 subchannels. + * + * "*" - matches any device + * + * Returns: #TRUE if @device matches one of the specs in @specs + */ +gboolean +nm_device_spec_match_list (NMDevice *device, const GSList *specs) +{ + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + if (!specs) + return FALSE; + + return NM_DEVICE_GET_CLASS (device)->spec_match_list (device, specs); +} + +static gboolean +spec_match_list (NMDevice *device, const GSList *specs) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + char *hwaddr_str; + gboolean matched = FALSE; + + if (nm_match_spec_string (specs, "*")) + return TRUE; + + if (priv->hw_addr_len) { + hwaddr_str = nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len); + matched = nm_match_spec_hwaddr (specs, hwaddr_str); + g_free (hwaddr_str); + } + + if (!matched) + matched = nm_match_spec_interface_name (specs, nm_device_get_iface (device)); + + return matched; +} + +static guint +get_hw_address_length (NMDevice *dev, gboolean *out_permanent) +{ + size_t len; + + if (nm_platform_link_get_address (nm_device_get_ip_ifindex (dev), &len)) + return len; + else + return 0; +} + +/***********************************************************/ + +#define DEFAULT_AUTOCONNECT TRUE + +static void +nm_device_init (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->type = NM_DEVICE_TYPE_UNKNOWN; + priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED; + priv->state = NM_DEVICE_STATE_UNMANAGED; + priv->state_reason = NM_DEVICE_STATE_REASON_NONE; + priv->dhcp_timeout = 0; + priv->rfkill_type = RFKILL_TYPE_UNKNOWN; + priv->autoconnect = DEFAULT_AUTOCONNECT; + priv->unmanaged_flags = NM_UNMANAGED_INTERNAL; + priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); + priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); +} + +static void +nm_device_config_device_interface_init (NMConfigDeviceInterface *iface) +{ + iface->spec_match_list = (gboolean (*) (NMConfigDevice *, const GSList *)) nm_device_spec_match_list; + iface->get_hw_address = (const guint8 * (*) (NMConfigDevice *, guint *)) nm_device_get_hw_address; +} + +/* + * Get driver info from SIOCETHTOOL ioctl() for 'iface' + * Returns driver and firmware versions to 'driver_version and' 'firmware_version' + */ +static gboolean +device_get_driver_info (const char *iface, char **driver_version, char **firmware_version) +{ + struct ethtool_drvinfo drvinfo; + struct ifreq req; + int fd; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_warn (LOGD_HW, "couldn't open control socket."); + return FALSE; + } + + /* Get driver and firmware version info */ + memset (&drvinfo, 0, sizeof (drvinfo)); + memset (&req, 0, sizeof (struct ifreq)); + strncpy (req.ifr_name, iface, IFNAMSIZ); + drvinfo.cmd = ETHTOOL_GDRVINFO; + req.ifr_data = &drvinfo; + + errno = 0; + if (ioctl (fd, SIOCETHTOOL, &req) < 0) { + nm_log_dbg (LOGD_HW, "SIOCETHTOOL ioctl() failed: cmd=ETHTOOL_GDRVINFO, iface=%s, errno=%d", + iface, errno); + close (fd); + return FALSE; + } + if (driver_version) + *driver_version = g_strdup (drvinfo.version); + if (firmware_version) + *firmware_version = g_strdup (drvinfo.fw_version); + + close (fd); + return TRUE; +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMDevice *dev; + NMDevicePrivate *priv; + NMPlatform *platform; + static guint32 id = 0; + + object = G_OBJECT_CLASS (nm_device_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + dev = NM_DEVICE (object); + priv = NM_DEVICE_GET_PRIVATE (dev); + + if (!priv->iface) { + nm_log_err (LOGD_DEVICE, "No device interface provided, ignoring"); + goto error; + } + + if (!priv->udi) { + /* Use a placeholder UDI until we get a real one */ + priv->udi = g_strdup_printf ("/virtual/device/placeholder/%d", id++); + } + + if (NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities) + priv->capabilities |= NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities (dev); + + priv->fw_manager = nm_firewall_manager_get (); + + device_get_driver_info (priv->iface, &priv->driver_version, &priv->firmware_version); + + /* Watch for external IP config changes */ + platform = nm_platform_get (); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), dev); + + return object; + +error: + g_object_unref (dev); + return NULL; +} + +static void +constructed (GObject *object) +{ + NMDevice *dev = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + + nm_device_update_hw_address (dev); + + if (NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address) + NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address (dev); + + if (NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address) + NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address (dev); + + /* Have to call update_initial_hw_address() before calling get_ignore_carrier() */ + if (device_has_capability (dev, NM_DEVICE_CAP_CARRIER_DETECT)) { + priv->ignore_carrier = nm_config_get_ignore_carrier (nm_config_get (), NM_CONFIG_DEVICE (dev)); + + check_carrier (dev); + nm_log_info (LOGD_HW, + "(%s): carrier is %s%s", + nm_device_get_iface (NM_DEVICE (dev)), + priv->carrier ? "ON" : "OFF", + priv->ignore_carrier ? " (but ignored)" : ""); + } else { + /* Fake online link when carrier detection is not available. */ + priv->carrier = TRUE; + } + + if (priv->ifindex > 0) { + priv->is_software = nm_platform_link_is_software (priv->ifindex); + priv->physical_port_id = nm_platform_link_get_physical_port_id (priv->ifindex); + } + + if (priv->ifindex > 0) + priv->mtu = nm_platform_link_get_mtu (priv->ifindex); + + priv->con_provider = nm_connection_provider_get (); + g_assert (priv->con_provider); + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_ADDED, + G_CALLBACK (cp_connection_added), + dev); + + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK (cp_connection_removed), + dev); + + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK (cp_connection_updated), + dev); + + G_OBJECT_CLASS (nm_device_parent_class)->constructed (object); +} + +static void +dispose (GObject *object) +{ + NMDevice *self = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMPlatform *platform; + + dispatcher_cleanup (self); + + _cleanup_generic_pre (self, FALSE); + + g_warn_if_fail (priv->slaves == NULL); + g_assert (priv->master_ready_id == 0); + + _cleanup_generic_post (self, FALSE); + + g_clear_pointer (&priv->ip6_saved_properties, g_hash_table_unref); + + if (priv->recheck_assume_id) { + g_source_remove (priv->recheck_assume_id); + priv->recheck_assume_id = 0; + } + + link_disconnect_action_cancel (self); + + if (priv->con_provider) { + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_added, self); + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_removed, self); + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_updated, self); + priv->con_provider = NULL; + } + + g_hash_table_unref (priv->available_connections); + priv->available_connections = NULL; + + if (priv->carrier_wait_id) { + g_source_remove (priv->carrier_wait_id); + priv->carrier_wait_id = 0; + } + + g_clear_object (&priv->queued_act_request); + + platform = nm_platform_get (); + g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ip_changed), self); + g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self); + + g_clear_object (&priv->fw_manager); + + G_OBJECT_CLASS (nm_device_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMDevice *self = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_slist_free_full (priv->pending_actions, g_free); + g_clear_pointer (&priv->physical_port_id, g_free); + g_free (priv->udi); + g_free (priv->path); + g_free (priv->iface); + g_free (priv->ip_iface); + g_free (priv->driver); + g_free (priv->driver_version); + g_free (priv->firmware_version); + g_free (priv->type_desc); + if (priv->dhcp_anycast_address) + g_byte_array_free (priv->dhcp_anycast_address, TRUE); + + G_OBJECT_CLASS (nm_device_parent_class)->finalize (object); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (object); + NMPlatformLink *platform_device; + const char *hw_addr; + + switch (prop_id) { + case PROP_PLATFORM_DEVICE: + platform_device = g_value_get_pointer (value); + if (platform_device) { + g_free (priv->udi); + priv->udi = g_strdup (platform_device->udi); + g_free (priv->iface); + priv->iface = g_strdup (platform_device->name); + priv->ifindex = platform_device->ifindex; + g_free (priv->driver); + priv->driver = g_strdup (platform_device->driver); + } + break; + case PROP_UDI: + if (g_value_get_string (value)) { + g_free (priv->udi); + priv->udi = g_value_dup_string (value); + } + break; + case PROP_IFACE: + if (g_value_get_string (value)) { + g_free (priv->iface); + priv->ifindex = 0; + priv->iface = g_value_dup_string (value); + + /* Only look up the ifindex if it appears to be an actual kernel + * interface name. eg Bluetooth devices won't have one until we know + * the IP interface. + */ + if (priv->iface && !strchr (priv->iface, ':')) { + priv->ifindex = nm_platform_link_get_ifindex (priv->iface); + if (priv->ifindex <= 0) + nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", priv->iface); + } + } + break; + case PROP_DRIVER: + if (g_value_get_string (value)) { + g_free (priv->driver); + priv->driver = g_value_dup_string (value); + } + break; + case PROP_DRIVER_VERSION: + g_free (priv->driver_version); + priv->driver_version = g_strdup (g_value_get_string (value)); + break; + case PROP_FIRMWARE_VERSION: + g_free (priv->firmware_version); + priv->firmware_version = g_strdup (g_value_get_string (value)); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint (value); + break; + case PROP_IP4_ADDRESS: + priv->ip4_address = g_value_get_uint (value); + break; + case PROP_AUTOCONNECT: + priv->autoconnect = g_value_get_boolean (value); + break; + case PROP_FIRMWARE_MISSING: + priv->firmware_missing = g_value_get_boolean (value); + break; + case PROP_DEVICE_TYPE: + g_return_if_fail (priv->type == NM_DEVICE_TYPE_UNKNOWN); + priv->type = g_value_get_uint (value); + break; + case PROP_TYPE_DESC: + g_free (priv->type_desc); + priv->type_desc = g_value_dup_string (value); + break; + case PROP_RFKILL_TYPE: + priv->rfkill_type = g_value_get_uint (value); + break; + case PROP_IS_MASTER: + priv->is_master = g_value_get_boolean (value); + break; + case PROP_HW_ADDRESS: + priv->hw_addr_len = nm_device_get_hw_address_length (NM_DEVICE (object), NULL); + + hw_addr = g_value_get_string (value); + if (!hw_addr) + break; + if (priv->hw_addr_len == 0) { + g_warn_if_fail (*hw_addr == '\0'); + break; + } + + if (!nm_utils_hwaddr_aton_len (hw_addr, priv->hw_addr, priv->hw_addr_len)) { + g_warning ("Could not parse hw-address '%s'", hw_addr); + memset (priv->hw_addr, 0, sizeof (priv->hw_addr)); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +#define DBUS_TYPE_STATE_REASON_STRUCT (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID)) + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDevice *self = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *ac_path = NULL; + GPtrArray *array; + GHashTableIter iter; + NMConnection *connection; + + switch (prop_id) { + case PROP_UDI: + g_value_set_string (value, priv->udi); + break; + case PROP_IFACE: + g_value_set_string (value, priv->iface); + break; + case PROP_IP_IFACE: + if (ip_config_valid (priv->state)) + g_value_set_string (value, nm_device_get_ip_iface (self)); + else + g_value_set_string (value, NULL); + break; + case PROP_IFINDEX: + g_value_set_int (value, priv->ifindex); + break; + case PROP_DRIVER: + g_value_set_string (value, priv->driver); + break; + case PROP_DRIVER_VERSION: + g_value_set_string (value, priv->driver_version); + break; + case PROP_FIRMWARE_VERSION: + g_value_set_string (value, priv->firmware_version); + break; + case PROP_CAPABILITIES: + g_value_set_uint (value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK)); + break; + case PROP_IP4_ADDRESS: + g_value_set_uint (value, priv->ip4_address); + break; + case PROP_CARRIER: + g_value_set_boolean (value, priv->carrier); + break; + case PROP_MTU: + g_value_set_uint (value, priv->mtu); + break; + case PROP_IP4_CONFIG: + if (ip_config_valid (priv->state) && priv->ip4_config) + g_value_set_boxed (value, nm_ip4_config_get_dbus_path (priv->ip4_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_DHCP4_CONFIG: + if (ip_config_valid (priv->state) && priv->dhcp4_config) + g_value_set_boxed (value, nm_dhcp4_config_get_dbus_path (priv->dhcp4_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_IP6_CONFIG: + if (ip_config_valid (priv->state) && priv->ip6_config) + g_value_set_boxed (value, nm_ip6_config_get_dbus_path (priv->ip6_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_DHCP6_CONFIG: + if (ip_config_valid (priv->state) && priv->dhcp6_config) + g_value_set_boxed (value, nm_dhcp6_config_get_dbus_path (priv->dhcp6_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_STATE: + g_value_set_uint (value, priv->state); + break; + case PROP_STATE_REASON: + g_value_take_boxed (value, dbus_g_type_specialized_construct (DBUS_TYPE_STATE_REASON_STRUCT)); + dbus_g_type_struct_set (value, 0, priv->state, 1, priv->state_reason, G_MAXUINT); + break; + case PROP_ACTIVE_CONNECTION: + if (priv->act_request) + ac_path = nm_active_connection_get_path (NM_ACTIVE_CONNECTION (priv->act_request)); + g_value_set_boxed (value, ac_path ? ac_path : "/"); + break; + case PROP_DEVICE_TYPE: + g_value_set_uint (value, priv->type); + break; + case PROP_MANAGED: + g_value_set_boolean (value, nm_device_get_managed (self)); + break; + case PROP_AUTOCONNECT: + g_value_set_boolean (value, priv->autoconnect); + break; + case PROP_FIRMWARE_MISSING: + g_value_set_boolean (value, priv->firmware_missing); + break; + case PROP_TYPE_DESC: + g_value_set_string (value, priv->type_desc); + break; + case PROP_RFKILL_TYPE: + g_value_set_uint (value, priv->rfkill_type); + break; + case PROP_AVAILABLE_CONNECTIONS: + array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections)); + g_hash_table_iter_init (&iter, priv->available_connections); + while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) + g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection))); + g_value_take_boxed (value, array); + break; + case PROP_PHYSICAL_PORT_ID: + g_value_set_string (value, priv->physical_port_id); + break; + case PROP_IS_MASTER: + g_value_set_boolean (value, priv->is_master); + break; + case PROP_MASTER: + g_value_set_object (value, priv->master); + break; + case PROP_HW_ADDRESS: + if (priv->hw_addr_len) + g_value_take_string (value, nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len)); + else + g_value_set_string (value, NULL); + break; + case PROP_HAS_PENDING_ACTION: + g_value_set_boolean (value, nm_device_has_pending_action (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_class_init (NMDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDevicePrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->constructor = constructor; + object_class->constructed = constructed; + + klass->link_changed = link_changed; + + klass->is_available = is_available; + klass->act_stage1_prepare = act_stage1_prepare; + klass->act_stage2_config = act_stage2_config; + klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start; + klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start; + klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout; + klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout; + klass->have_any_ready_slaves = have_any_ready_slaves; + + klass->spec_match_list = spec_match_list; + klass->can_auto_connect = can_auto_connect; + klass->check_connection_compatible = check_connection_compatible; + klass->check_connection_available = check_connection_available; + klass->is_up = is_up; + klass->bring_up = bring_up; + klass->take_down = take_down; + klass->carrier_changed = carrier_changed; + klass->get_hw_address_length = get_hw_address_length; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_PLATFORM_DEVICE, + g_param_spec_pointer (NM_DEVICE_PLATFORM_DEVICE, + "Platform Device", + "NMPlatform device object", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_UDI, + g_param_spec_string (NM_DEVICE_UDI, + "UDI", + "Unique Device Identifier", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (object_class, PROP_IFACE, + g_param_spec_string (NM_DEVICE_IFACE, + "Interface", + "Interface", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_IP_IFACE, + g_param_spec_string (NM_DEVICE_IP_IFACE, + "IP Interface", + "IP Interface", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_DRIVER, + g_param_spec_string (NM_DEVICE_DRIVER, + "Driver", + "Driver", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_DRIVER_VERSION, + g_param_spec_string (NM_DEVICE_DRIVER_VERSION, + "Driver Version", + "Driver Version", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_FIRMWARE_VERSION, + g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION, + "Firmware Version", + "Firmware Version", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_CAPABILITIES, + g_param_spec_uint (NM_DEVICE_CAPABILITIES, + "Capabilities", + "Capabilities", + 0, G_MAXUINT32, NM_DEVICE_CAP_NONE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_CARRIER, + g_param_spec_boolean (NM_DEVICE_CARRIER, + "Carrier", + "Carrier", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_MTU, + g_param_spec_uint (NM_DEVICE_MTU, + "MTU", + "MTU", + 0, G_MAXUINT32, 1500, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_IP4_ADDRESS, + g_param_spec_uint (NM_DEVICE_IP4_ADDRESS, + "IP4 address", + "IP4 address", + 0, G_MAXUINT32, 0, /* FIXME */ + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_IP4_CONFIG, + g_param_spec_boxed (NM_DEVICE_IP4_CONFIG, + "IP4 Config", + "IP4 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_DHCP4_CONFIG, + g_param_spec_boxed (NM_DEVICE_DHCP4_CONFIG, + "DHCP4 Config", + "DHCP4 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_IP6_CONFIG, + g_param_spec_boxed (NM_DEVICE_IP6_CONFIG, + "IP6 Config", + "IP6 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_DHCP6_CONFIG, + g_param_spec_boxed (NM_DEVICE_DHCP6_CONFIG, + "DHCP6 Config", + "DHCP6 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_STATE, + g_param_spec_uint (NM_DEVICE_STATE, + "State", + "State", + 0, G_MAXUINT32, NM_DEVICE_STATE_UNKNOWN, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_STATE_REASON, + g_param_spec_boxed (NM_DEVICE_STATE_REASON, + "StateReason", + "StateReason", + DBUS_TYPE_STATE_REASON_STRUCT, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_ACTIVE_CONNECTION, + g_param_spec_boxed (NM_DEVICE_ACTIVE_CONNECTION, + "ActiveConnection", + "ActiveConnection", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_DEVICE_TYPE, + g_param_spec_uint (NM_DEVICE_DEVICE_TYPE, + "DeviceType", + "DeviceType", + 0, G_MAXUINT32, NM_DEVICE_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_MANAGED, + g_param_spec_boolean (NM_DEVICE_MANAGED, + "Managed", + "Managed", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_AUTOCONNECT, + g_param_spec_boolean (NM_DEVICE_AUTOCONNECT, + "Autoconnect", + "Autoconnect", + DEFAULT_AUTOCONNECT, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_FIRMWARE_MISSING, + g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING, + "FirmwareMissing", + "Firmware missing", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_TYPE_DESC, + g_param_spec_string (NM_DEVICE_TYPE_DESC, + "Type Description", + "Device type description", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_RFKILL_TYPE, + g_param_spec_uint (NM_DEVICE_RFKILL_TYPE, + "Rfkill Type", + "Type of rfkill switch (if any) supported by this device", + RFKILL_TYPE_WLAN, + RFKILL_TYPE_MAX, + RFKILL_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_IFINDEX, + g_param_spec_int (NM_DEVICE_IFINDEX, + "Ifindex", + "Ifindex", + 0, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_AVAILABLE_CONNECTIONS, + g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS, + "AvailableConnections", + "AvailableConnections", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_PHYSICAL_PORT_ID, + g_param_spec_string (NM_DEVICE_PHYSICAL_PORT_ID, + "PhysicalPortId", + "PhysicalPortId", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_IS_MASTER, + g_param_spec_boolean (NM_DEVICE_IS_MASTER, + "IsMaster", + "IsMaster", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_MASTER, + g_param_spec_object (NM_DEVICE_MASTER, + "Master", + "Master", + NM_TYPE_DEVICE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_HW_ADDRESS, + g_param_spec_string (NM_DEVICE_HW_ADDRESS, + "Hardware Address", + "Hardware address", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_HAS_PENDING_ACTION, + g_param_spec_boolean (NM_DEVICE_HAS_PENDING_ACTION, + "Has pending action", + "Has pending action", + FALSE, + G_PARAM_READABLE)); + + /* Signals */ + signals[STATE_CHANGED] = + g_signal_new ("state-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMDeviceClass, state_changed), + NULL, NULL, NULL, + G_TYPE_NONE, 3, + G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); + + signals[AUTOCONNECT_ALLOWED] = + g_signal_new ("autoconnect-allowed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + autoconnect_allowed_accumulator, NULL, NULL, + G_TYPE_BOOLEAN, 0); + + signals[AUTH_REQUEST] = + g_signal_new (NM_DEVICE_AUTH_REQUEST, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + /* dbus-glib context, connection, permission, allow_interaction, callback, user_data */ + G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER, G_TYPE_POINTER); + + signals[IP4_CONFIG_CHANGED] = + g_signal_new (NM_DEVICE_IP4_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); + + signals[IP6_CONFIG_CHANGED] = + g_signal_new (NM_DEVICE_IP6_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); + + signals[REMOVED] = + g_signal_new (NM_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[RECHECK_AUTO_ACTIVATE] = + g_signal_new (NM_DEVICE_RECHECK_AUTO_ACTIVATE, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[RECHECK_ASSUME] = + g_signal_new (NM_DEVICE_RECHECK_ASSUME, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_object_info); + + dbus_g_error_domain_register (NM_DEVICE_ERROR, NULL, NM_TYPE_DEVICE_ERROR); +} + diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h new file mode 100644 index 000000000..f74486e6f --- /dev/null +++ b/src/devices/nm-device.h @@ -0,0 +1,363 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2005 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef NM_DEVICE_H +#define NM_DEVICE_H + +#include <glib-object.h> +#include <dbus/dbus-glib.h> +#include <netinet/in.h> + +#include "NetworkManager.h" +#include "nm-types.h" +#include "nm-activation-request.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "nm-dhcp4-config.h" +#include "nm-dhcp6-config.h" +#include "nm-connection.h" +#include "nm-rfkill-manager.h" +#include "nm-connection-provider.h" +#include "nm-platform.h" + +/* Properties */ +#define NM_DEVICE_UDI "udi" +#define NM_DEVICE_IFACE "interface" +#define NM_DEVICE_IP_IFACE "ip-interface" +#define NM_DEVICE_DRIVER "driver" +#define NM_DEVICE_DRIVER_VERSION "driver-version" +#define NM_DEVICE_FIRMWARE_VERSION "firmware-version" +#define NM_DEVICE_CAPABILITIES "capabilities" +#define NM_DEVICE_CARRIER "carrier" +#define NM_DEVICE_IP4_ADDRESS "ip4-address" +#define NM_DEVICE_IP4_CONFIG "ip4-config" +#define NM_DEVICE_DHCP4_CONFIG "dhcp4-config" +#define NM_DEVICE_IP6_CONFIG "ip6-config" +#define NM_DEVICE_DHCP6_CONFIG "dhcp6-config" +#define NM_DEVICE_STATE "state" +#define NM_DEVICE_STATE_REASON "state-reason" +#define NM_DEVICE_ACTIVE_CONNECTION "active-connection" +#define NM_DEVICE_DEVICE_TYPE "device-type" /* ugh */ +#define NM_DEVICE_MANAGED "managed" +#define NM_DEVICE_AUTOCONNECT "autoconnect" +#define NM_DEVICE_FIRMWARE_MISSING "firmware-missing" +#define NM_DEVICE_AVAILABLE_CONNECTIONS "available-connections" +#define NM_DEVICE_PHYSICAL_PORT_ID "physical-port-id" +#define NM_DEVICE_MTU "mtu" +#define NM_DEVICE_TYPE_DESC "type-desc" /* Internal only */ +#define NM_DEVICE_RFKILL_TYPE "rfkill-type" /* Internal only */ +#define NM_DEVICE_IFINDEX "ifindex" /* Internal only */ +#define NM_DEVICE_IS_MASTER "is-master" /* Internal only */ +#define NM_DEVICE_MASTER "master" /* Internal only */ +#define NM_DEVICE_HW_ADDRESS "hw-address" /* Internal only */ +#define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */ + +/* Internal signals */ +#define NM_DEVICE_AUTH_REQUEST "auth-request" +#define NM_DEVICE_IP4_CONFIG_CHANGED "ip4-config-changed" +#define NM_DEVICE_IP6_CONFIG_CHANGED "ip6-config-changed" +#define NM_DEVICE_REMOVED "removed" +#define NM_DEVICE_RECHECK_AUTO_ACTIVATE "recheck-auto-activate" +#define NM_DEVICE_RECHECK_ASSUME "recheck-assume" + + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE (nm_device_get_type ()) +#define NM_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE, NMDevice)) +#define NM_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE, NMDeviceClass)) +#define NM_IS_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE)) +#define NM_IS_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE)) +#define NM_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE, NMDeviceClass)) + +typedef enum NMActStageReturn NMActStageReturn; + +typedef enum { + NM_DEVICE_ERROR_CONNECTION_ACTIVATING = 0, /*< nick=ConnectionActivating >*/ + NM_DEVICE_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_DEVICE_ERROR_NOT_ACTIVE, /*< nick=NotActive >*/ +} NMDeviceError; + +struct _NMDevice { + GObject parent; +}; + +typedef struct { + GObjectClass parent; + + const char *connection_type; + + void (*state_changed) (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason); + + void (* link_changed) (NMDevice *self, NMPlatformLink *info); + + /* Hardware state (IFF_UP) */ + gboolean (*is_up) (NMDevice *self); + gboolean (*bring_up) (NMDevice *self, gboolean *no_firmware); + gboolean (*take_down) (NMDevice *self); + + /* Carrier state (IFF_LOWER_UP) */ + void (*carrier_changed) (NMDevice *, gboolean carrier); + + void (* update_hw_address) (NMDevice *self); + void (* update_permanent_hw_address) (NMDevice *self); + void (* update_initial_hw_address) (NMDevice *self); + guint (* get_hw_address_length) (NMDevice *self, gboolean *out_permanent); + + guint32 (* get_generic_capabilities) (NMDevice *self); + + gboolean (* is_available) (NMDevice *self); + + gboolean (* get_enabled) (NMDevice *self); + + void (* set_enabled) (NMDevice *self, gboolean enabled); + + gboolean (* can_auto_connect) (NMDevice *self, + NMConnection *connection, + char **specific_object); + + /* Checks whether the connection is compatible with the device using + * only the devices type and characteristics. Does not use any live + * network information like WiFi/WiMAX scan lists etc. + */ + gboolean (* check_connection_compatible) (NMDevice *self, NMConnection *connection); + + /* Checks whether the connection is likely available to be activated, + * including any live network information like scan lists. The connection + * is checked against the object defined by @specific_object, if given. + * Returns TRUE if the connection is available; FALSE if not. + */ + gboolean (* check_connection_available) (NMDevice *self, + NMConnection *connection, + const char *specific_object); + + /* Same as check_connection_available() but called if the connection + * is not present in the activating-connections array during activation, + * to give the device a chance to allow/deny the activation. This is a + * hack only meant for hidden WiFi networks. + */ + gboolean (* check_connection_available_wifi_hidden) (NMDevice *self, + NMConnection *connection); + + gboolean (* complete_connection) (NMDevice *self, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error); + + NMActStageReturn (* act_stage1_prepare) (NMDevice *self, + NMDeviceStateReason *reason); + NMActStageReturn (* act_stage2_config) (NMDevice *self, + NMDeviceStateReason *reason); + NMActStageReturn (* act_stage3_ip4_config_start) (NMDevice *self, + NMIP4Config **out_config, + NMDeviceStateReason *reason); + NMActStageReturn (* act_stage3_ip6_config_start) (NMDevice *self, + NMIP6Config **out_config, + NMDeviceStateReason *reason); + NMActStageReturn (* act_stage4_ip4_config_timeout) (NMDevice *self, + NMDeviceStateReason *reason); + NMActStageReturn (* act_stage4_ip6_config_timeout) (NMDevice *self, + NMDeviceStateReason *reason); + + /* Called right before IP config is set; use for setting MTU etc */ + void (* ip4_config_pre_commit) (NMDevice *self, NMIP4Config *config); + void (* ip6_config_pre_commit) (NMDevice *self); + + void (* deactivate) (NMDevice *self); + + gboolean (* spec_match_list) (NMDevice *self, const GSList *specs); + + /* Update the connection with currently configured L2 settings */ + void (* update_connection) (NMDevice *device, NMConnection *connection); + + gboolean (* enslave_slave) (NMDevice *self, + NMDevice *slave, + NMConnection *connection, + gboolean configure); + + gboolean (* release_slave) (NMDevice *self, + NMDevice *slave, + gboolean configure); + + gboolean (* have_any_ready_slaves) (NMDevice *self, + const GSList *slaves); + + gboolean (* component_added) (NMDevice *self, GObject *component); + + gboolean (* owns_iface) (NMDevice *self, const char *iface); +} NMDeviceClass; + + +typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device, + DBusGMethodInvocation *context, + GError *error, + gpointer user_data); + +GType nm_device_get_type (void); + +const char * nm_device_get_path (NMDevice *dev); +void nm_device_dbus_export (NMDevice *device); + +const char * nm_device_get_udi (NMDevice *dev); +const char * nm_device_get_iface (NMDevice *dev); +int nm_device_get_ifindex (NMDevice *dev); +gboolean nm_device_is_software (NMDevice *dev); +const char * nm_device_get_ip_iface (NMDevice *dev); +int nm_device_get_ip_ifindex(NMDevice *dev); +const char * nm_device_get_driver (NMDevice *dev); +const char * nm_device_get_driver_version (NMDevice *dev); +const char * nm_device_get_type_desc (NMDevice *dev); +NMDeviceType nm_device_get_device_type (NMDevice *dev); + +int nm_device_get_priority (NMDevice *dev); + +const guint8 * nm_device_get_hw_address (NMDevice *dev, guint *out_len); + +NMDHCP4Config * nm_device_get_dhcp4_config (NMDevice *dev); +NMDHCP6Config * nm_device_get_dhcp6_config (NMDevice *dev); + +NMIP4Config * nm_device_get_ip4_config (NMDevice *dev); +void nm_device_set_vpn4_config (NMDevice *dev, NMIP4Config *config); + +NMIP6Config * nm_device_get_ip6_config (NMDevice *dev); +void nm_device_set_vpn6_config (NMDevice *dev, NMIP6Config *config); + +void nm_device_capture_initial_config (NMDevice *dev); + +/* Master */ +GSList * nm_device_master_get_slaves (NMDevice *dev); + +/* Slave */ +NMDevice * nm_device_get_master (NMDevice *dev); + +NMActRequest * nm_device_get_act_request (NMDevice *dev); +NMConnection * nm_device_get_connection (NMDevice *dev); + +gboolean nm_device_is_available (NMDevice *dev); +gboolean nm_device_has_carrier (NMDevice *dev); + +NMConnection * nm_device_generate_connection (NMDevice *device); + +NMConnection * nm_device_get_best_auto_connection (NMDevice *dev, + GSList *connections, + char **specific_object); + +gboolean nm_device_complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connection, + GError **error); + +gboolean nm_device_check_connection_compatible (NMDevice *device, NMConnection *connection); + +gboolean nm_device_can_assume_active_connection (NMDevice *device); + +gboolean nm_device_spec_match_list (NMDevice *device, const GSList *specs); + +gboolean nm_device_is_activating (NMDevice *dev); +gboolean nm_device_autoconnect_allowed (NMDevice *self); + +NMDeviceState nm_device_get_state (NMDevice *device); + +gboolean nm_device_get_enabled (NMDevice *device); + +void nm_device_set_enabled (NMDevice *device, gboolean enabled); + +RfKillType nm_device_get_rfkill_type (NMDevice *device); + +/** + * NMUnmanagedFlags: + * @NM_UNMANAGED_NONE: placeholder value + * @NM_UNMANAGED_DEFAULT: %TRUE when unmanaged by default (ie, Generic devices) + * @NM_UNMANAGED_INTERNAL: %TRUE when unmanaged by internal decision (ie, + * because NM is sleeping or not managed for some other reason) + * @NM_UNMANAGED_USER: %TRUE when unmanaged by user decision (via unmanaged-specs) + */ +typedef enum { + NM_UNMANAGED_NONE = 0x00, + NM_UNMANAGED_DEFAULT = 0x01, + NM_UNMANAGED_INTERNAL = 0x02, + NM_UNMANAGED_USER = 0x04, + + /* Boundary value */ + __NM_UNMANAGED_LAST, + NM_UNMANAGED_LAST = __NM_UNMANAGED_LAST - 1, +} NMUnmanagedFlags; + +gboolean nm_device_get_managed (NMDevice *device); +gboolean nm_device_get_unmanaged_flag (NMDevice *device, NMUnmanagedFlags flag); +void nm_device_set_unmanaged (NMDevice *device, + NMUnmanagedFlags flag, + gboolean unmanaged, + NMDeviceStateReason reason); +void nm_device_set_unmanaged_quitting (NMDevice *device); +void nm_device_set_initial_unmanaged_flag (NMDevice *device, + NMUnmanagedFlags flag, + gboolean unmanaged); + +gboolean nm_device_get_is_nm_owned (NMDevice *device); +void nm_device_set_nm_owned (NMDevice *device); + +gboolean nm_device_get_autoconnect (NMDevice *device); + +void nm_device_handle_autoip4_event (NMDevice *self, + const char *event, + const char *address); + +void nm_device_state_changed (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason); + +void nm_device_queue_state (NMDevice *self, + NMDeviceState state, + NMDeviceStateReason reason); + +gboolean nm_device_get_firmware_missing (NMDevice *self); + +void nm_device_queue_activation (NMDevice *device, NMActRequest *req); + +gboolean nm_device_supports_vlans (NMDevice *device); + +gboolean nm_device_add_pending_action (NMDevice *device, const char *action, gboolean assert_not_yet_pending); +gboolean nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending); +gboolean nm_device_has_pending_action (NMDevice *device); + +GPtrArray *nm_device_get_available_connections (NMDevice *device, + const char *specific_object); + +gboolean nm_device_connection_is_available (NMDevice *device, + NMConnection *connection, + gboolean allow_device_override); + +gboolean nm_device_notify_component_added (NMDevice *device, GObject *component); + +gboolean nm_device_owns_iface (NMDevice *device, const char *iface); + +G_END_DECLS + +/* For testing only */ +extern const char* nm_device_autoipd_helper_path; + +#endif /* NM_DEVICE_H */ diff --git a/src/devices/wifi/Makefile.am b/src/devices/wifi/Makefile.am new file mode 100644 index 000000000..5f3ce286b --- /dev/null +++ b/src/devices/wifi/Makefile.am @@ -0,0 +1,78 @@ +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +SUBDIRS=. tests + +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_srcdir}/src/supplicant-manager \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-wifi"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +GLIB_GENERATED = nm-wifi-enum-types.h nm-wifi-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_wifi_enum_types_sources = \ + $(srcdir)/nm-device-wifi.h \ + $(srcdir)/nm-wifi-ap.h \ + $(srcdir)/nm-device-olpc-mesh.h + +glue_sources = \ + nm-device-wifi-glue.h \ + nm-device-olpc-mesh-glue.h + +%-glue.h: $(top_srcdir)/introspection/%.xml + $(AM_V_GEN) dbus-binding-tool --prefix=$(subst -,_,$(subst -glue.h,,$@)) --mode=glib-server --output=$@ $< + +BUILT_SOURCES = $(GLIB_GENERATED) $(glue_sources) + +pkglib_LTLIBRARIES = libnm-device-plugin-wifi.la + +libnm_device_plugin_wifi_la_SOURCES = \ + nm-wifi-factory.c \ + nm-device-wifi.c \ + nm-device-wifi.h \ + nm-wifi-ap.c \ + nm-wifi-ap.h \ + nm-wifi-ap-utils.c \ + nm-wifi-ap-utils.h \ + nm-device-olpc-mesh.c \ + nm-device-olpc-mesh.h \ + \ + $(BUILT_SOURCES) + +SYMBOL_VIS_FILE=$(srcdir)/exports.ver + +libnm_device_plugin_wifi_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_wifi_la_LIBADD = \ + $(DBUS_LIBS) \ + $(GUDEV_LIBS) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = $(SYMBOL_VIS_FILE) + +if ENABLE_TESTS + +check-local: + $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wifi.so $(SYMBOL_VIS_FILE) + +endif + diff --git a/src/devices/wifi/Makefile.in b/src/devices/wifi/Makefile.in new file mode 100644 index 000000000..cbf6826be --- /dev/null +++ b/src/devices/wifi/Makefile.in @@ -0,0 +1,983 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/devices/wifi +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/build-aux/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \ + $(top_srcdir)/m4/compiler_warnings.m4 \ + $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gnome-code-coverage.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkglibdir)" +LTLIBRARIES = $(pkglib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libnm_device_plugin_wifi_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am__objects_1 = nm-wifi-enum-types.lo +am__objects_2 = +am__objects_3 = $(am__objects_1) $(am__objects_2) +am_libnm_device_plugin_wifi_la_OBJECTS = nm-wifi-factory.lo \ + nm-device-wifi.lo nm-wifi-ap.lo nm-wifi-ap-utils.lo \ + nm-device-olpc-mesh.lo $(am__objects_3) +libnm_device_plugin_wifi_la_OBJECTS = \ + $(am_libnm_device_plugin_wifi_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libnm_device_plugin_wifi_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libnm_device_plugin_wifi_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libnm_device_plugin_wifi_la_SOURCES) +DIST_SOURCES = $(libnm_device_plugin_wifi_la_SOURCES) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CKDB_PATH = @CKDB_PATH@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@ +DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SYS_DIR = @DBUS_SYS_DIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHCLIENT_PATH = @DHCLIENT_PATH@ +DHCPCD_PATH = @DHCPCD_PATH@ +DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@ +DLLTOOL = @DLLTOOL@ +DNSMASQ_PATH = @DNSMASQ_PATH@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MAKEFILE = @GLIB_MAKEFILE@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +IPTABLES_PATH = @IPTABLES_PATH@ +IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@ +IWMX_SDK_LIBS = @IWMX_SDK_LIBS@ +KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDL = @LIBDL@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBNDP_CFLAGS = @LIBNDP_CFLAGS@ +LIBNDP_LIBS = @LIBNDP_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@ +LIBSOUP_LIBS = @LIBSOUP_LIBS@ +LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@ +LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MM_GLIB_CFLAGS = @MM_GLIB_CFLAGS@ +MM_GLIB_LIBS = @MM_GLIB_LIBS@ +MOC = @MOC@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEWT_CFLAGS = @NEWT_CFLAGS@ +NEWT_LIBS = @NEWT_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NM_MAJOR_VERSION = @NM_MAJOR_VERSION@ +NM_MICRO_VERSION = @NM_MICRO_VERSION@ +NM_MINOR_VERSION = @NM_MINOR_VERSION@ +NM_MODIFY_SYSTEM_POLICY = @NM_MODIFY_SYSTEM_POLICY@ +NM_VERSION = @NM_VERSION@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POLKIT_CFLAGS = @POLKIT_CFLAGS@ +POLKIT_LIBS = @POLKIT_LIBS@ +POSUB = @POSUB@ +PPPD_PATH = @PPPD_PATH@ +PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@ +PPPOE_PATH = @PPPOE_PATH@ +QT_CFLAGS = @QT_CFLAGS@ +QT_LIBS = @QT_LIBS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@ +SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@ +SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@ +SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +SYSTEM_CA_PATH = @SYSTEM_CA_PATH@ +UDEV_BASE_DIR = @UDEV_BASE_DIR@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND_RULES = @VALGRIND_RULES@ +VAPIGEN = @VAPIGEN@ +VAPIGEN_MAKEFILE = @VAPIGEN_MAKEFILE@ +VAPIGEN_VAPIDIR = @VAPIGEN_VAPIDIR@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nmbinary = @nmbinary@ +nmconfdir = @nmconfdir@ +nmdatadir = @nmdatadir@ +nmrundir = @nmrundir@ +nmstatedir = @nmstatedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +with_dhclient = @with_dhclient@ +with_dhcpcd = @with_dhcpcd@ +with_netconfig = @with_netconfig@ +with_resolvconf = @with_resolvconf@ +with_valgrind = @with_valgrind@ +SUBDIRS = . tests +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_srcdir}/src/supplicant-manager \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-wifi"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +GLIB_GENERATED = nm-wifi-enum-types.h nm-wifi-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_wifi_enum_types_sources = \ + $(srcdir)/nm-device-wifi.h \ + $(srcdir)/nm-wifi-ap.h \ + $(srcdir)/nm-device-olpc-mesh.h + +glue_sources = \ + nm-device-wifi-glue.h \ + nm-device-olpc-mesh-glue.h + +BUILT_SOURCES = $(GLIB_GENERATED) $(glue_sources) +pkglib_LTLIBRARIES = libnm-device-plugin-wifi.la +libnm_device_plugin_wifi_la_SOURCES = \ + nm-wifi-factory.c \ + nm-device-wifi.c \ + nm-device-wifi.h \ + nm-wifi-ap.c \ + nm-wifi-ap.h \ + nm-wifi-ap-utils.c \ + nm-wifi-ap-utils.h \ + nm-device-olpc-mesh.c \ + nm-device-olpc-mesh.h \ + \ + $(BUILT_SOURCES) + +SYMBOL_VIS_FILE = $(srcdir)/exports.ver +libnm_device_plugin_wifi_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_wifi_la_LIBADD = \ + $(DBUS_LIBS) \ + $(GUDEV_LIBS) + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-recursive + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/devices/wifi/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/devices/wifi/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ + } + +uninstall-pkglibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ + done + +clean-pkglibLTLIBRARIES: + -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) + @list='$(pkglib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libnm-device-plugin-wifi.la: $(libnm_device_plugin_wifi_la_OBJECTS) $(libnm_device_plugin_wifi_la_DEPENDENCIES) $(EXTRA_libnm_device_plugin_wifi_la_DEPENDENCIES) + $(AM_V_CCLD)$(libnm_device_plugin_wifi_la_LINK) -rpath $(pkglibdir) $(libnm_device_plugin_wifi_la_OBJECTS) $(libnm_device_plugin_wifi_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-device-olpc-mesh.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-device-wifi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wifi-ap-utils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wifi-ap.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wifi-enum-types.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wifi-factory.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +@ENABLE_TESTS_FALSE@check-local: +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-recursive +all-am: Makefile $(LTLIBRARIES) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(pkglibdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: install-pkglibLTLIBRARIES + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-pkglibLTLIBRARIES + +.MAKE: $(am__recursive_targets) all check check-am install install-am \ + install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am check-local clean clean-generic clean-libtool \ + clean-pkglibLTLIBRARIES cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-pkglibLTLIBRARIES \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am \ + uninstall-pkglibLTLIBRARIES + +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +%-glue.h: $(top_srcdir)/introspection/%.xml + $(AM_V_GEN) dbus-binding-tool --prefix=$(subst -,_,$(subst -glue.h,,$@)) --mode=glib-server --output=$@ $< + +@ENABLE_TESTS_TRUE@check-local: +@ENABLE_TESTS_TRUE@ $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wifi.so $(SYMBOL_VIS_FILE) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/devices/wifi/exports.ver b/src/devices/wifi/exports.ver new file mode 100644 index 000000000..d2c451244 --- /dev/null +++ b/src/devices/wifi/exports.ver @@ -0,0 +1,7 @@ +{ +global: + nm_device_factory_create; + nm_device_factory_get_device_type; +local: + *; +}; diff --git a/src/devices/wifi/nm-device-olpc-mesh-glue.h b/src/devices/wifi/nm-device-olpc-mesh-glue.h new file mode 100644 index 000000000..04b5e83f3 --- /dev/null +++ b/src/devices/wifi/nm-device-olpc-mesh-glue.h @@ -0,0 +1,73 @@ +/* Generated by dbus-binding-tool; do not edit! */ + + +#ifndef __dbus_glib_marshal_nm_device_olpc_mesh_MARSHAL_H__ +#define __dbus_glib_marshal_nm_device_olpc_mesh_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +G_END_DECLS + +#endif /* __dbus_glib_marshal_nm_device_olpc_mesh_MARSHAL_H__ */ + +#include <dbus/dbus-glib.h> +static const DBusGMethodInfo dbus_glib_nm_device_olpc_mesh_methods[] = { +}; + +const DBusGObjectInfo dbus_glib_nm_device_olpc_mesh_object_info = { 1, + dbus_glib_nm_device_olpc_mesh_methods, + 0, +"\0", +"org.freedesktop.NetworkManager.Device.OlpcMesh\0PropertiesChanged\0\0", +"org.freedesktop.NetworkManager.Device.OlpcMesh\0HwAddress\0hw_address\0read\0org.freedesktop.NetworkManager.Device.OlpcMesh\0Companion\0companion\0read\0org.freedesktop.NetworkManager.Device.OlpcMesh\0ActiveChannel\0active_channel\0read\0\0" +}; + diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c new file mode 100644 index 000000000..85214a700 --- /dev/null +++ b/src/devices/wifi/nm-device-olpc-mesh.c @@ -0,0 +1,585 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * Dan Williams <dcbw@redhat.com> + * Sjoerd Simons <sjoerd.simons@collabora.co.uk> + * Daniel Drake <dsd@laptop.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2005 - 2014 Red Hat, Inc. + * (C) Copyright 2008 Collabora Ltd. + * (C) Copyright 2009 One Laptop per Child + */ + +#include "config.h" +#include <glib.h> +#include <glib/gi18n.h> +#include <dbus/dbus.h> +#include <netinet/in.h> +#include <string.h> +#include <net/ethernet.h> +#include <netinet/ether.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <linux/if.h> +#include <sys/ioctl.h> +#include <errno.h> + +#include "nm-device.h" +#include "nm-device-wifi.h" +#include "nm-device-olpc-mesh.h" +#include "nm-device-private.h" +#include "nm-utils.h" +#include "nm-logging.h" +#include "NetworkManagerUtils.h" +#include "nm-activation-request.h" +#include "nm-setting-connection.h" +#include "nm-setting-olpc-mesh.h" +#include "nm-manager.h" +#include "nm-enum-types.h" +#include "nm-dbus-manager.h" +#include "nm-wifi-enum-types.h" + +/* This is a bug; but we can't really change API now... */ +#include "NetworkManagerVPN.h" + + +#include "nm-device-olpc-mesh-glue.h" + +G_DEFINE_TYPE (NMDeviceOlpcMesh, nm_device_olpc_mesh, NM_TYPE_DEVICE) + +#define NM_DEVICE_OLPC_MESH_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMeshPrivate)) + +enum { + PROP_0, + PROP_COMPANION, + PROP_ACTIVE_CHANNEL, + + LAST_PROP +}; + +struct _NMDeviceOlpcMeshPrivate { + NMDevice *companion; + gboolean stage1_waiting; +}; + +/*******************************************************************/ + +static GQuark +nm_olpc_mesh_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-mesh-error"); + return quark; +} + +#define NM_OLPC_MESH_ERROR (nm_olpc_mesh_error_quark ()) + +/*******************************************************************/ + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMSettingConnection *s_con; + NMSettingOlpcMesh *s_mesh; + + if (!NM_DEVICE_CLASS (nm_device_olpc_mesh_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_OLPC_MESH_SETTING_NAME)) + return FALSE; + + s_mesh = nm_connection_get_setting_olpc_mesh (connection); + if (!s_mesh) + return FALSE; + + return TRUE; +} + +static gboolean +can_auto_connect (NMDevice *device, + NMConnection *connection, + char **specific_object) +{ + return FALSE; +} + +#define DEFAULT_SSID "olpc-mesh" + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMSettingOlpcMesh *s_mesh; + GByteArray *tmp; + + s_mesh = nm_connection_get_setting_olpc_mesh (connection); + if (!s_mesh) { + s_mesh = (NMSettingOlpcMesh *) nm_setting_olpc_mesh_new (); + nm_connection_add_setting (connection, NM_SETTING (s_mesh)); + } + + if (!nm_setting_olpc_mesh_get_ssid (s_mesh)) { + tmp = g_byte_array_sized_new (strlen (DEFAULT_SSID)); + g_byte_array_append (tmp, (const guint8 *) DEFAULT_SSID, strlen (DEFAULT_SSID)); + g_object_set (G_OBJECT (s_mesh), NM_SETTING_OLPC_MESH_SSID, tmp, NULL); + g_byte_array_free (tmp, TRUE); + } + + if (!nm_setting_olpc_mesh_get_dhcp_anycast_address (s_mesh)) { + const guint8 anycast[ETH_ALEN] = { 0xC0, 0x27, 0xC0, 0x27, 0xC0, 0x27 }; + + tmp = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (tmp, anycast, sizeof (anycast)); + g_object_set (G_OBJECT (s_mesh), NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS, tmp, NULL); + g_byte_array_free (tmp, TRUE); + + } + + nm_utils_complete_generic (connection, + NM_SETTING_OLPC_MESH_SETTING_NAME, + existing_connections, + _("Mesh %d"), + NULL, + FALSE); /* No IPv6 by default */ + + return TRUE; +} + +/****************************************************************************/ + +static NMActStageReturn +act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (dev); + NMActStageReturn ret; + gboolean scanning; + + ret = NM_DEVICE_CLASS (nm_device_olpc_mesh_parent_class)->act_stage1_prepare (dev, reason); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) + return ret; + + /* disconnect companion device, if it is connected */ + if (nm_device_get_act_request (NM_DEVICE (priv->companion))) { + nm_log_info (LOGD_OLPC, "(%s): disconnecting companion device %s", + nm_device_get_iface (dev), + nm_device_get_iface (priv->companion)); + /* FIXME: VPN stuff here is a bug; but we can't really change API now... */ + nm_device_state_changed (NM_DEVICE (priv->companion), + NM_DEVICE_STATE_DISCONNECTED, + NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED); + nm_log_info (LOGD_OLPC, "(%s): companion %s disconnected", + nm_device_get_iface (dev), + nm_device_get_iface (priv->companion)); + } + + + /* wait with continuing configuration untill the companion device is done scanning */ + g_object_get (priv->companion, "scanning", &scanning, NULL); + if (scanning) { + priv->stage1_waiting = TRUE; + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +_mesh_set_channel (NMDeviceOlpcMesh *self, guint32 channel) +{ + int ifindex = nm_device_get_ifindex (NM_DEVICE (self)); + + if (nm_platform_mesh_get_channel (ifindex) != channel) { + if (nm_platform_mesh_set_channel (ifindex, channel)) + g_object_notify (G_OBJECT (self), NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL); + } +} + +static NMActStageReturn +act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (dev); + NMConnection *connection; + NMSettingOlpcMesh *s_mesh; + guint32 channel; + const GByteArray *anycast_addr_array; + guint8 *anycast_addr = NULL; + + connection = nm_device_get_connection (dev); + g_assert (connection); + + s_mesh = nm_connection_get_setting_olpc_mesh (connection); + g_assert (s_mesh); + + channel = nm_setting_olpc_mesh_get_channel (s_mesh); + if (channel != 0) + _mesh_set_channel (self, channel); + nm_platform_mesh_set_ssid (nm_device_get_ifindex (dev), + nm_setting_olpc_mesh_get_ssid (s_mesh)); + + anycast_addr_array = nm_setting_olpc_mesh_get_dhcp_anycast_address (s_mesh); + if (anycast_addr_array) + anycast_addr = anycast_addr_array->data; + + nm_device_set_dhcp_anycast_address (dev, anycast_addr); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static gboolean +is_available (NMDevice *dev) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (dev); + + if (!NM_DEVICE_OLPC_MESH_GET_PRIVATE (self)->companion) { + nm_log_dbg (LOGD_WIFI, "(%s): not available because companion not found", + nm_device_get_iface (dev)); + return FALSE; + } + + return TRUE; +} + +/*******************************************************************/ + +static void +companion_cleanup (NMDeviceOlpcMesh *self) +{ + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self); + + if (priv->companion) { + g_signal_handlers_disconnect_by_data (priv->companion, self); + g_clear_object (&priv->companion); + } + g_object_notify (G_OBJECT (self), NM_DEVICE_OLPC_MESH_COMPANION); +} + +static void +companion_notify_cb (NMDeviceWifi *companion, GParamSpec *pspec, gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self); + gboolean scanning; + + if (!priv->stage1_waiting) + return; + + g_object_get (companion, "scanning", &scanning, NULL); + + if (!scanning) { + priv->stage1_waiting = FALSE; + nm_device_activate_schedule_stage2_device_config (NM_DEVICE (self)); + } +} + +/* disconnect from mesh if someone starts using the companion */ +static void +companion_state_changed_cb (NMDeviceWifi *companion, + NMDeviceState state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data); + NMDeviceState self_state = nm_device_get_state (NM_DEVICE (self)); + + if ( self_state < NM_DEVICE_STATE_PREPARE + || self_state > NM_DEVICE_STATE_ACTIVATED + || state < NM_DEVICE_STATE_PREPARE + || state > NM_DEVICE_STATE_ACTIVATED) + return; + + nm_log_dbg (LOGD_OLPC, "(%s): disconnecting mesh due to companion connectivity", + nm_device_get_iface (NM_DEVICE (self))); + /* FIXME: VPN stuff here is a bug; but we can't really change API now... */ + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_DISCONNECTED, + NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED); +} + +static gboolean +companion_scan_allowed_cb (NMDeviceWifi *companion, gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data); + NMDeviceState state = nm_device_get_state (NM_DEVICE (self)); + + /* Don't allow the companion to scan while configuring the mesh interface */ + return (state < NM_DEVICE_STATE_PREPARE) || (state > NM_DEVICE_STATE_IP_CONFIG); +} + +static gboolean +companion_autoconnect_allowed_cb (NMDeviceWifi *companion, gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data); + NMDeviceState state = nm_device_get_state (NM_DEVICE (self)); + + /* Don't allow the companion to autoconnect while a mesh connection is + * active */ + return (state < NM_DEVICE_STATE_PREPARE) || (state > NM_DEVICE_STATE_ACTIVATED); +} + +static gboolean +check_companion (NMDeviceOlpcMesh *self, NMDevice *other) +{ + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self); + const guint8 *my_addr, *their_addr; + guint their_addr_len; + + if (!NM_IS_DEVICE_WIFI (other)) + return FALSE; + + my_addr = nm_device_get_hw_address (NM_DEVICE (self), NULL); + their_addr = nm_device_get_hw_address (other, &their_addr_len); + if ( (their_addr_len != ETH_ALEN) + || (memcmp (my_addr, their_addr, ETH_ALEN) != 0)) + return FALSE; + + g_assert (priv->companion == NULL); + priv->companion = g_object_ref (other); + + nm_log_info (LOGD_OLPC, "(%s): found companion WiFi device %s", + nm_device_get_iface (NM_DEVICE (self)), + nm_device_get_iface (other)); + + g_signal_connect (G_OBJECT (other), "state-changed", + G_CALLBACK (companion_state_changed_cb), self); + + g_signal_connect (G_OBJECT (other), "notify::scanning", + G_CALLBACK (companion_notify_cb), self); + + g_signal_connect (G_OBJECT (other), "scanning-allowed", + G_CALLBACK (companion_scan_allowed_cb), self); + + g_signal_connect (G_OBJECT (other), "autoconnect-allowed", + G_CALLBACK (companion_autoconnect_allowed_cb), self); + + g_object_notify (G_OBJECT (self), NM_DEVICE_OLPC_MESH_COMPANION); + + return TRUE; +} + +static void +device_added_cb (NMManager *manager, NMDevice *other, gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self); + + if (!priv->companion && check_companion (self, other)) { + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_NONE); + nm_device_remove_pending_action (NM_DEVICE (self), "waiting for companion", TRUE); + } +} + +static void +device_removed_cb (NMManager *manager, NMDevice *other, gpointer user_data) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data); + + if (other == NM_DEVICE_OLPC_MESH_GET_PRIVATE (self)->companion) + companion_cleanup (self); +} + +static void +find_companion (NMDeviceOlpcMesh *self) +{ + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self); + const GSList *list; + + if (priv->companion) + return; + + nm_device_add_pending_action (NM_DEVICE (self), "waiting for companion", TRUE); + + /* Try to find the companion if it's already known to the NMManager */ + for (list = nm_manager_get_devices (nm_manager_get ()); list ; list = g_slist_next (list)) { + if (check_companion (self, NM_DEVICE (list->data))) { + nm_device_queue_state (NM_DEVICE (self), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_NONE); + nm_device_remove_pending_action (NM_DEVICE (self), "waiting for companion", TRUE); + break; + } + } +} + +static void +state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + if (new_state == NM_DEVICE_STATE_UNAVAILABLE) + find_companion (NM_DEVICE_OLPC_MESH (device)); +} + +/*******************************************************************/ + +NMDevice * +nm_device_olpc_mesh_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_OLPC_MESH, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "802.11 OLPC Mesh", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_OLPC_MESH, + NULL); +} + +static void +nm_device_olpc_mesh_init (NMDeviceOlpcMesh * self) +{ +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + GObjectClass *klass; + NMDeviceOlpcMesh *self; + NMDeviceWifiCapabilities caps; + + klass = G_OBJECT_CLASS (nm_device_olpc_mesh_parent_class); + object = klass->constructor (type, n_construct_params, construct_params); + if (!object) + return NULL; + + self = NM_DEVICE_OLPC_MESH (object); + + nm_log_dbg (LOGD_HW | LOGD_OLPC, "(%s): kernel ifindex %d", + nm_device_get_iface (NM_DEVICE (self)), + nm_device_get_ifindex (NM_DEVICE (self))); + + if (!nm_platform_wifi_get_capabilities (nm_device_get_ifindex (NM_DEVICE (self)), &caps)) { + nm_log_warn (LOGD_HW | LOGD_OLPC, "(%s): failed to initialize WiFi driver", + nm_device_get_iface (NM_DEVICE (self))); + g_object_unref (object); + return NULL; + } + + g_signal_connect (nm_manager_get (), "device-added", G_CALLBACK (device_added_cb), self); + g_signal_connect (nm_manager_get (), "device-removed", G_CALLBACK (device_removed_cb), self); + + /* shorter timeout for mesh connectivity */ + nm_device_set_dhcp_timeout (NM_DEVICE (self), 20); + return object; +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceOlpcMesh *device = NM_DEVICE_OLPC_MESH (object); + NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (device); + + switch (prop_id) { + case PROP_COMPANION: + if (priv->companion) + g_value_set_boxed (value, nm_device_get_path (priv->companion)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_ACTIVE_CHANNEL: + g_value_set_uint (value, nm_platform_mesh_get_channel (nm_device_get_ifindex (NM_DEVICE (device)))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (object); + + companion_cleanup (self); + g_signal_handlers_disconnect_by_func (nm_manager_get (), G_CALLBACK (device_added_cb), self); + g_signal_handlers_disconnect_by_func (nm_manager_get (), G_CALLBACK (device_removed_cb), self); + + G_OBJECT_CLASS (nm_device_olpc_mesh_parent_class)->dispose (object); +} + +static void +nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceOlpcMeshPrivate)); + + object_class->constructor = constructor; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->can_auto_connect = can_auto_connect; + parent_class->complete_connection = complete_connection; + + parent_class->is_available = is_available; + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->act_stage2_config = act_stage2_config; + + parent_class->state_changed = state_changed; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_COMPANION, + g_param_spec_boxed (NM_DEVICE_OLPC_MESH_COMPANION, + "Companion device", + "Companion device object path", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_ACTIVE_CHANNEL, + g_param_spec_uint (NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL, + "Active channel", + "Active channel", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_olpc_mesh_object_info); + + dbus_g_error_domain_register (NM_OLPC_MESH_ERROR, NULL, NM_TYPE_OLPC_MESH_ERROR); +} + diff --git a/src/devices/wifi/nm-device-olpc-mesh.h b/src/devices/wifi/nm-device-olpc-mesh.h new file mode 100644 index 000000000..c25dd8e53 --- /dev/null +++ b/src/devices/wifi/nm-device-olpc-mesh.h @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ + +/* NetworkManager -- Network link manager + * + * Dan Williams <dcbw@redhat.com> + * Sjoerd Simons <sjoerd.simons@collabora.co.uk> + * Daniel Drake <dsd@laptop.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2005 Red Hat, Inc. + * (C) Copyright 2008 Collabora Ltd. + * (C) Copyright 2009 One Laptop per Child + */ + +#ifndef NM_DEVICE_OLPC_MESH_H +#define NM_DEVICE_OLPC_MESH_H + +#include <glib-object.h> +#include <dbus/dbus.h> + +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_OLPC_MESH (nm_device_olpc_mesh_get_type ()) +#define NM_DEVICE_OLPC_MESH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMesh)) +#define NM_DEVICE_OLPC_MESH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMeshClass)) +#define NM_IS_DEVICE_OLPC_MESH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OLPC_MESH)) +#define NM_IS_DEVICE_OLPC_MESH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_OLPC_MESH)) +#define NM_DEVICE_OLPC_MESH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMeshClass)) + +typedef enum +{ + NM_OLPC_MESH_ERROR_CONNECTION_NOT_MESH = 0, /*< nick=ConnectionNotMesh >*/ + NM_OLPC_MESH_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_OLPC_MESH_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ +} NMOlpcMeshError; + +#define NM_DEVICE_OLPC_MESH_COMPANION "companion" +#define NM_DEVICE_OLPC_MESH_BITRATE "bitrate" +#define NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL "active-channel" + +#ifndef NM_DEVICE_OLPC_MESH_DEFINED +#define NM_DEVICE_OLPC_MESH_DEFINED +typedef struct _NMDeviceOlpcMesh NMDeviceOlpcMesh; +#endif + +typedef struct _NMDeviceOlpcMeshClass NMDeviceOlpcMeshClass; +typedef struct _NMDeviceOlpcMeshPrivate NMDeviceOlpcMeshPrivate; + +struct _NMDeviceOlpcMesh +{ + NMDevice parent; +}; + +struct _NMDeviceOlpcMeshClass +{ + NMDeviceClass parent; + +}; + + +GType nm_device_olpc_mesh_get_type (void); + +NMDevice *nm_device_olpc_mesh_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_OLPC_MESH_H */ diff --git a/src/devices/wifi/nm-device-wifi-glue.h b/src/devices/wifi/nm-device-wifi-glue.h new file mode 100644 index 000000000..3d0f63db7 --- /dev/null +++ b/src/devices/wifi/nm-device-wifi-glue.h @@ -0,0 +1,167 @@ +/* Generated by dbus-binding-tool; do not edit! */ + + +#ifndef __dbus_glib_marshal_nm_device_wifi_MARSHAL_H__ +#define __dbus_glib_marshal_nm_device_wifi_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* BOOLEAN:POINTER,POINTER */ +extern void dbus_glib_marshal_nm_device_wifi_BOOLEAN__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +void +dbus_glib_marshal_nm_device_wifi_BOOLEAN__POINTER_POINTER (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER_POINTER) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_BOOLEAN__POINTER_POINTER callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_pointer (param_values + 2), + data2); + + g_value_set_boolean (return_value, v_return); +} + +/* NONE:BOXED,POINTER */ +extern void dbus_glib_marshal_nm_device_wifi_VOID__BOXED_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +void +dbus_glib_marshal_nm_device_wifi_VOID__BOXED_POINTER (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__BOXED_POINTER) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_VOID__BOXED_POINTER callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__BOXED_POINTER) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_boxed (param_values + 1), + g_marshal_value_peek_pointer (param_values + 2), + data2); +} +#define dbus_glib_marshal_nm_device_wifi_NONE__BOXED_POINTER dbus_glib_marshal_nm_device_wifi_VOID__BOXED_POINTER + +G_END_DECLS + +#endif /* __dbus_glib_marshal_nm_device_wifi_MARSHAL_H__ */ + +#include <dbus/dbus-glib.h> +static const DBusGMethodInfo dbus_glib_nm_device_wifi_methods[] = { + { (GCallback) impl_device_get_access_points, dbus_glib_marshal_nm_device_wifi_BOOLEAN__POINTER_POINTER, 0 }, + { (GCallback) impl_device_get_all_access_points, dbus_glib_marshal_nm_device_wifi_BOOLEAN__POINTER_POINTER, 89 }, + { (GCallback) impl_device_request_scan, dbus_glib_marshal_nm_device_wifi_NONE__BOXED_POINTER, 181 }, +}; + +const DBusGObjectInfo dbus_glib_nm_device_wifi_object_info = { 1, + dbus_glib_nm_device_wifi_methods, + 3, +"org.freedesktop.NetworkManager.Device.Wireless\0GetAccessPoints\0S\0access_points\0O\0F\0N\0ao\0\0org.freedesktop.NetworkManager.Device.Wireless\0GetAllAccessPoints\0S\0access_points\0O\0F\0N\0ao\0\0org.freedesktop.NetworkManager.Device.Wireless\0RequestScan\0A\0options\0I\0a{sv}\0\0\0", +"org.freedesktop.NetworkManager.Device.Wireless\0PropertiesChanged\0org.freedesktop.NetworkManager.Device.Wireless\0AccessPointAdded\0org.freedesktop.NetworkManager.Device.Wireless\0AccessPointRemoved\0\0", +"org.freedesktop.NetworkManager.Device.Wireless\0HwAddress\0hw_address\0read\0org.freedesktop.NetworkManager.Device.Wireless\0PermHwAddress\0perm_hw_address\0read\0org.freedesktop.NetworkManager.Device.Wireless\0Mode\0mode\0read\0org.freedesktop.NetworkManager.Device.Wireless\0Bitrate\0bitrate\0read\0org.freedesktop.NetworkManager.Device.Wireless\0AccessPoints\0access_points\0read\0org.freedesktop.NetworkManager.Device.Wireless\0ActiveAccessPoint\0active_access_point\0read\0org.freedesktop.NetworkManager.Device.Wireless\0WirelessCapabilities\0wireless_capabilities\0read\0\0" +}; + diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c new file mode 100644 index 000000000..95173cf47 --- /dev/null +++ b/src/devices/wifi/nm-device-wifi.c @@ -0,0 +1,3502 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2005 - 2012 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include <glib.h> +#include <glib/gi18n.h> +#include <dbus/dbus.h> +#include <netinet/in.h> +#include <string.h> +#include <net/ethernet.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <linux/sockios.h> +#include <linux/ethtool.h> +#include <sys/ioctl.h> +#include <netinet/ether.h> +#include <errno.h> + +#include "nm-glib-compat.h" +#include "nm-dbus-manager.h" +#include "nm-device.h" +#include "nm-device-wifi.h" +#include "nm-device-private.h" +#include "nm-utils.h" +#include "nm-logging.h" +#include "NetworkManagerUtils.h" +#include "nm-activation-request.h" +#include "nm-supplicant-manager.h" +#include "nm-supplicant-interface.h" +#include "nm-supplicant-config.h" +#include "nm-setting-connection.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-8021x.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-platform.h" +#include "nm-manager-auth.h" +#include "nm-settings-connection.h" +#include "nm-enum-types.h" +#include "nm-dbus-glib-types.h" +#include "nm-wifi-enum-types.h" +#include "nm-connection-provider.h" + + +static gboolean impl_device_get_access_points (NMDeviceWifi *device, + GPtrArray **aps, + GError **err); + +static gboolean impl_device_get_all_access_points (NMDeviceWifi *device, + GPtrArray **aps, + GError **err); + +static void impl_device_request_scan (NMDeviceWifi *device, + GHashTable *options, + DBusGMethodInvocation *context); + +#include "nm-device-wifi-glue.h" + + +/* All of these are in seconds */ +#define SCAN_INTERVAL_MIN 3 +#define SCAN_INTERVAL_STEP 20 +#define SCAN_INTERVAL_MAX 120 + +#define WIRELESS_SECRETS_TRIES "wireless-secrets-tries" + +G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE) + +#define NM_DEVICE_WIFI_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_WIFI, NMDeviceWifiPrivate)) + + +enum { + PROP_0, + PROP_PERM_HW_ADDRESS, + PROP_MODE, + PROP_BITRATE, + PROP_ACCESS_POINTS, + PROP_ACTIVE_ACCESS_POINT, + PROP_CAPABILITIES, + PROP_SCANNING, + + LAST_PROP +}; + +enum { + ACCESS_POINT_ADDED, + ACCESS_POINT_REMOVED, + SCANNING_ALLOWED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _NMDeviceWifiPrivate { + gboolean disposed; + + guint8 perm_hw_addr[ETH_ALEN]; /* Permanent MAC address */ + guint8 initial_hw_addr[ETH_ALEN]; /* Initial MAC address (as seen when NM starts) */ + + gint8 invalid_strength_counter; + + GSList * ap_list; + NMAccessPoint * current_ap; + guint32 rate; + gboolean enabled; /* rfkilled or not */ + + gint32 scheduled_scan_time; + guint8 scan_interval; /* seconds */ + guint pending_scan_id; + guint scanlist_cull_id; + gboolean requested_scan; + + NMSupplicantManager *sup_mgr; + NMSupplicantInterface *sup_iface; + guint sup_timeout_id; /* supplicant association timeout */ + + gboolean ssid_found; + NM80211Mode mode; + + guint32 failed_link_count; + guint periodic_source_id; + guint link_timeout_id; + + NMDeviceWifiCapabilities capabilities; +}; + +static gboolean check_scanning_allowed (NMDeviceWifi *self); + +static void schedule_scan (NMDeviceWifi *self, gboolean backoff); + +static void cancel_pending_scan (NMDeviceWifi *self); + +static void cleanup_association_attempt (NMDeviceWifi * self, + gboolean disconnect); + +static void supplicant_iface_state_cb (NMSupplicantInterface *iface, + guint32 new_state, + guint32 old_state, + int disconnect_reason, + gpointer user_data); + +static void supplicant_iface_new_bss_cb (NMSupplicantInterface * iface, + const char *object_path, + GHashTable *properties, + NMDeviceWifi * self); + +static void supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface, + const char *object_path, + GHashTable *properties, + NMDeviceWifi *self); + +static void supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface, + const char *object_path, + NMDeviceWifi *self); + +static void supplicant_iface_scan_done_cb (NMSupplicantInterface * iface, + gboolean success, + NMDeviceWifi * self); + +static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface, + GParamSpec * pspec, + NMDeviceWifi * self); + +static void schedule_scanlist_cull (NMDeviceWifi *self); + +static gboolean request_wireless_scan (gpointer user_data); + +static void remove_access_point (NMDeviceWifi *device, NMAccessPoint *ap); + +static void remove_supplicant_interface_error_handler (NMDeviceWifi *self); + +/*****************************************************************/ + +#define NM_WIFI_ERROR (nm_wifi_error_quark ()) + +static GQuark +nm_wifi_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-wifi-error"); + return quark; +} + +/*****************************************************************/ + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + GObjectClass *klass; + NMDeviceWifi *self; + NMDeviceWifiPrivate *priv; + + klass = G_OBJECT_CLASS (nm_device_wifi_parent_class); + object = klass->constructor (type, n_construct_params, construct_params); + if (!object) + return NULL; + + self = NM_DEVICE_WIFI (object); + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + nm_log_dbg (LOGD_HW | LOGD_WIFI, "(%s): kernel ifindex %d", + nm_device_get_iface (NM_DEVICE (self)), + nm_device_get_ifindex (NM_DEVICE (self))); + + if (!nm_platform_wifi_get_capabilities (nm_device_get_ifindex (NM_DEVICE (self)), + &priv->capabilities)) { + nm_log_warn (LOGD_HW | LOGD_WIFI, "(%s): failed to initialize WiFi driver", + nm_device_get_iface (NM_DEVICE (self))); + g_object_unref (object); + return NULL; + } + + if (priv->capabilities & NM_WIFI_DEVICE_CAP_AP) { + nm_log_info (LOGD_HW | LOGD_WIFI, "(%s): driver supports Access Point (AP) mode", + nm_device_get_iface (NM_DEVICE (self))); + } + + /* Connect to the supplicant manager */ + priv->sup_mgr = nm_supplicant_manager_get (); + g_assert (priv->sup_mgr); + + return object; +} + +static gboolean +supplicant_interface_acquire (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + g_return_val_if_fail (self != NULL, FALSE); + /* interface already acquired? */ + g_return_val_if_fail (priv->sup_iface == NULL, TRUE); + + priv->sup_iface = nm_supplicant_manager_iface_get (priv->sup_mgr, + nm_device_get_iface (NM_DEVICE (self)), + TRUE); + if (priv->sup_iface == NULL) { + nm_log_err (LOGD_WIFI, "Couldn't initialize supplicant interface for %s.", + nm_device_get_iface (NM_DEVICE (self))); + return FALSE; + } + + if (nm_supplicant_interface_get_state (priv->sup_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY) + nm_device_add_pending_action (NM_DEVICE (self), "waiting for supplicant", TRUE); + + g_signal_connect (priv->sup_iface, + NM_SUPPLICANT_INTERFACE_STATE, + G_CALLBACK (supplicant_iface_state_cb), + self); + g_signal_connect (priv->sup_iface, + NM_SUPPLICANT_INTERFACE_NEW_BSS, + G_CALLBACK (supplicant_iface_new_bss_cb), + self); + g_signal_connect (priv->sup_iface, + NM_SUPPLICANT_INTERFACE_BSS_UPDATED, + G_CALLBACK (supplicant_iface_bss_updated_cb), + self); + g_signal_connect (priv->sup_iface, + NM_SUPPLICANT_INTERFACE_BSS_REMOVED, + G_CALLBACK (supplicant_iface_bss_removed_cb), + self); + g_signal_connect (priv->sup_iface, + NM_SUPPLICANT_INTERFACE_SCAN_DONE, + G_CALLBACK (supplicant_iface_scan_done_cb), + self); + g_signal_connect (priv->sup_iface, + "notify::scanning", + G_CALLBACK (supplicant_iface_notify_scanning_cb), + self); + + return TRUE; +} + +static void +supplicant_interface_release (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + + g_return_if_fail (self != NULL); + + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + cancel_pending_scan (self); + + /* Reset the scan interval to be pretty frequent when disconnected */ + priv->scan_interval = SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP; + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): reset scanning interval to %d seconds", + nm_device_get_iface (NM_DEVICE (self)), + priv->scan_interval); + + if (priv->scanlist_cull_id) { + g_source_remove (priv->scanlist_cull_id); + priv->scanlist_cull_id = 0; + } + + if (priv->sup_iface) { + remove_supplicant_interface_error_handler (self); + + /* Clear supplicant interface signal handlers */ + g_signal_handlers_disconnect_by_data (priv->sup_iface, self); + + /* Tell the supplicant to disconnect from the current AP */ + nm_supplicant_interface_disconnect (priv->sup_iface); + + nm_supplicant_manager_iface_release (priv->sup_mgr, priv->sup_iface); + priv->sup_iface = NULL; + } +} + +static NMAccessPoint * +get_ap_by_path (NMDeviceWifi *self, const char *path) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList *iter; + + if (!path) + return NULL; + + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) { + if (g_strcmp0 (path, nm_ap_get_dbus_path (NM_AP (iter->data))) == 0) + return NM_AP (iter->data); + } + return NULL; +} + +static NMAccessPoint * +get_ap_by_supplicant_path (NMDeviceWifi *self, const char *path) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList *iter; + + if (!path) + return NULL; + + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) { + if (g_strcmp0 (path, nm_ap_get_supplicant_path (NM_AP (iter->data))) == 0) + return NM_AP (iter->data); + } + return NULL; +} + +static NMAccessPoint * +find_active_ap (NMDeviceWifi *self, + NMAccessPoint *ignore_ap, + gboolean match_hidden) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + const char *iface = nm_device_get_iface (NM_DEVICE (self)); + int ifindex = nm_device_get_ifindex (NM_DEVICE (self)); + struct ether_addr bssid; + GByteArray *ssid; + GSList *iter; + int i = 0; + NMAccessPoint *match_nofreq = NULL, *active_ap = NULL; + gboolean found_a_band = FALSE; + gboolean found_bg_band = FALSE; + NM80211Mode devmode; + guint32 devfreq; + + nm_platform_wifi_get_bssid (ifindex, &bssid); + nm_log_dbg (LOGD_WIFI, "(%s): active BSSID: %02x:%02x:%02x:%02x:%02x:%02x", + iface, + bssid.ether_addr_octet[0], bssid.ether_addr_octet[1], + bssid.ether_addr_octet[2], bssid.ether_addr_octet[3], + bssid.ether_addr_octet[4], bssid.ether_addr_octet[5]); + + if (!nm_ethernet_address_is_valid (&bssid)) + return NULL; + + ssid = nm_platform_wifi_get_ssid (ifindex); + nm_log_dbg (LOGD_WIFI, "(%s): active SSID: %s%s%s", + iface, + ssid ? "'" : "", + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)", + ssid ? "'" : ""); + + devmode = nm_platform_wifi_get_mode (ifindex); + devfreq = nm_platform_wifi_get_frequency (ifindex); + + /* When matching hidden APs, do a second pass that ignores the SSID check, + * because NM might not yet know the SSID of the hidden AP in the scan list + * and therefore it won't get matched the first time around. + */ + while (i++ < (match_hidden ? 2 : 1)) { + nm_log_dbg (LOGD_WIFI, " Pass #%d %s", i, i > 1 ? "(ignoring SSID)" : ""); + + /* Find this SSID + BSSID in the device's AP list */ + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) { + NMAccessPoint *ap = NM_AP (iter->data); + const struct ether_addr *ap_bssid = nm_ap_get_address (ap); + const GByteArray *ap_ssid = nm_ap_get_ssid (ap); + NM80211Mode apmode; + guint32 apfreq; + + nm_log_dbg (LOGD_WIFI, " AP: %s%s%s %02x:%02x:%02x:%02x:%02x:%02x", + ap_ssid ? "'" : "", + ap_ssid ? nm_utils_escape_ssid (ap_ssid->data, ap_ssid->len) : "(none)", + ap_ssid ? "'" : "", + ap_bssid->ether_addr_octet[0], ap_bssid->ether_addr_octet[1], + ap_bssid->ether_addr_octet[2], ap_bssid->ether_addr_octet[3], + ap_bssid->ether_addr_octet[4], ap_bssid->ether_addr_octet[5]); + + if (ap == ignore_ap) { + nm_log_dbg (LOGD_WIFI, " ignored"); + continue; + } + + if (memcmp (bssid.ether_addr_octet, ap_bssid->ether_addr_octet, ETH_ALEN)) { + nm_log_dbg (LOGD_WIFI, " BSSID mismatch"); + continue; + } + + if ((i == 0) && !nm_utils_same_ssid (ssid, ap_ssid, TRUE)) { + nm_log_dbg (LOGD_WIFI, " SSID mismatch"); + continue; + } + + apmode = nm_ap_get_mode (ap); + if (devmode != apmode) { + nm_log_dbg (LOGD_WIFI, " mode mismatch (device %d, ap %d)", + devmode, apmode); + continue; + } + + apfreq = nm_ap_get_freq (ap); + if (devfreq != apfreq) { + nm_log_dbg (LOGD_WIFI, " frequency mismatch (device %u, ap %u)", + devfreq, apfreq); + + if (match_nofreq == NULL) + match_nofreq = ap; + + if (apfreq > 4000) + found_a_band = TRUE; + else if (apfreq > 2000) + found_bg_band = TRUE; + continue; + } + + // FIXME: handle security settings here too + nm_log_dbg (LOGD_WIFI, " matched"); + active_ap = ap; + goto done; + } + } + + /* Some proprietary drivers (wl.o) report tuned frequency (like when + * scanning) instead of the associated AP's frequency. This is a great + * example of how WEXT is underspecified. We use frequency to find the + * active AP in the scan list because some configurations use the same + * SSID/BSSID on the 2GHz and 5GHz bands simultaneously, and we need to + * make sure we get the right AP in the right band. This configuration + * is uncommon though, and the frequency check penalizes closed drivers we + * can't fix. Because we're not total dicks, ignore the frequency condition + * if the associated BSSID/SSID exists only in one band since that's most + * likely the AP we want. Sometimes wl.o returns a frequency of 0, so if + * we can't match the AP based on frequency at all, just give up. + */ + if (match_nofreq && ((found_a_band != found_bg_band) || (devfreq == 0))) { + const struct ether_addr *ap_bssid = nm_ap_get_address (match_nofreq); + const GByteArray *ap_ssid = nm_ap_get_ssid (match_nofreq); + + nm_log_dbg (LOGD_WIFI, " matched %s%s%s %02x:%02x:%02x:%02x:%02x:%02x", + ap_ssid ? "'" : "", + ap_ssid ? nm_utils_escape_ssid (ap_ssid->data, ap_ssid->len) : "(none)", + ap_ssid ? "'" : "", + ap_bssid->ether_addr_octet[0], ap_bssid->ether_addr_octet[1], + ap_bssid->ether_addr_octet[2], ap_bssid->ether_addr_octet[3], + ap_bssid->ether_addr_octet[4], ap_bssid->ether_addr_octet[5]); + + active_ap = match_nofreq; + goto done; + } + + nm_log_dbg (LOGD_WIFI, " No matching AP found."); + +done: + if (ssid) + g_byte_array_free (ssid, TRUE); + return active_ap; +} + +static void +update_seen_bssids_cache (NMDeviceWifi *self, NMAccessPoint *ap) +{ + NMConnection *connection; + + g_return_if_fail (NM_IS_DEVICE_WIFI (self)); + + if (ap == NULL) + return; + + /* Don't cache the BSSID for Ad-Hoc APs */ + if (nm_ap_get_mode (ap) != NM_802_11_MODE_INFRA) + return; + + if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED) { + connection = nm_device_get_connection (NM_DEVICE (self)); + if (connection) { + nm_settings_connection_add_seen_bssid (NM_SETTINGS_CONNECTION (connection), + nm_ap_get_address (ap)); + } + } +} + +static void +set_current_ap (NMDeviceWifi *self, NMAccessPoint *new_ap, gboolean recheck_available_connections, gboolean force_remove_old_ap) +{ + NMDeviceWifiPrivate *priv; + NMAccessPoint *old_ap; + + g_return_if_fail (NM_IS_DEVICE_WIFI (self)); + + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + old_ap = priv->current_ap; + + if (old_ap == new_ap) + return; + + if (new_ap) { + priv->current_ap = g_object_ref (new_ap); + + /* Move the current AP to the front of the scan list. Since we + * do a lot of searches looking for the current AP, it saves + * time to have it in front. + */ + priv->ap_list = g_slist_remove (priv->ap_list, new_ap); + priv->ap_list = g_slist_prepend (priv->ap_list, new_ap); + + /* Update seen BSSIDs cache */ + update_seen_bssids_cache (self, priv->current_ap); + } else + priv->current_ap = NULL; + + if (old_ap) { + NM80211Mode mode = nm_ap_get_mode (old_ap); + + if (force_remove_old_ap || mode == NM_802_11_MODE_ADHOC || mode == NM_802_11_MODE_AP || nm_ap_get_fake (old_ap)) { + remove_access_point (self, old_ap); + if (recheck_available_connections) + nm_device_recheck_available_connections (NM_DEVICE (self)); + } + g_object_unref (old_ap); + } + + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT); +} + +static void +periodic_update (NMDeviceWifi *self, NMAccessPoint *ignore_ap) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + int ifindex = nm_device_get_ifindex (NM_DEVICE (self)); + NMAccessPoint *new_ap; + guint32 new_rate; + int percent; + NMDeviceState state; + guint32 supplicant_state; + + /* BSSID and signal strength have meaningful values only if the device + * is activated and not scanning. + */ + state = nm_device_get_state (NM_DEVICE (self)); + if (state != NM_DEVICE_STATE_ACTIVATED) + return; + + /* Only update current AP if we're actually talking to something, otherwise + * assume the old one (if any) is still valid until we're told otherwise or + * the connection fails. + */ + supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface); + if ( supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING + || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED + || nm_supplicant_interface_get_scanning (priv->sup_iface)) + return; + + /* In AP mode we currently have nothing to do. */ + if (priv->mode == NM_802_11_MODE_AP) + return; + + /* In IBSS mode, most newer firmware/drivers do "BSS coalescing" where + * multiple IBSS stations using the same SSID will eventually switch to + * using the same BSSID to avoid network segmentation. When this happens, + * the card's reported BSSID will change, but the new BSS may not + * be in the scan list, since scanning isn't done in ad-hoc mode for + * various reasons. So pull the BSSID from the card and update the + * current AP with it, if the current AP is adhoc. + */ + if (priv->current_ap && (nm_ap_get_mode (priv->current_ap) == NM_802_11_MODE_ADHOC)) { + struct ether_addr bssid = { {0x0, 0x0, 0x0, 0x0, 0x0, 0x0} }; + + nm_platform_wifi_get_bssid (ifindex, &bssid); + /* 0x02 means "locally administered" and should be OR-ed into + * the first byte of IBSS BSSIDs. + */ + if ( (bssid.ether_addr_octet[0] & 0x02) + && nm_ethernet_address_is_valid (&bssid)) + nm_ap_set_address (priv->current_ap, &bssid); + } + + new_ap = find_active_ap (self, ignore_ap, FALSE); + if (new_ap) { + /* Try to smooth out the strength. Atmel cards, for example, will give no strength + * one second and normal strength the next. + */ + percent = nm_platform_wifi_get_quality (ifindex); + if (percent >= 0 || ++priv->invalid_strength_counter > 3) { + nm_ap_set_strength (new_ap, (gint8) percent); + priv->invalid_strength_counter = 0; + } + } + + if (new_ap != priv->current_ap) { + const struct ether_addr *new_bssid = NULL; + const GByteArray *new_ssid = NULL; + const struct ether_addr *old_bssid = NULL; + const GByteArray *old_ssid = NULL; + char *old_addr = NULL, *new_addr = NULL; + + if (new_ap) { + new_bssid = nm_ap_get_address (new_ap); + new_addr = nm_utils_hwaddr_ntoa (new_bssid, ARPHRD_ETHER); + new_ssid = nm_ap_get_ssid (new_ap); + } + + if (priv->current_ap) { + old_bssid = nm_ap_get_address (priv->current_ap); + old_addr = nm_utils_hwaddr_ntoa (old_bssid, ARPHRD_ETHER); + old_ssid = nm_ap_get_ssid (priv->current_ap); + } + + nm_log_info (LOGD_WIFI, "(%s): roamed from BSSID %s (%s) to %s (%s)", + nm_device_get_iface (NM_DEVICE (self)), + old_addr ? old_addr : "(none)", + old_ssid ? nm_utils_escape_ssid (old_ssid->data, old_ssid->len) : "(none)", + new_addr ? new_addr : "(none)", + new_ssid ? nm_utils_escape_ssid (new_ssid->data, new_ssid->len) : "(none)"); + g_free (old_addr); + g_free (new_addr); + + set_current_ap (self, new_ap, TRUE, FALSE); + } + + new_rate = nm_platform_wifi_get_rate (ifindex); + if (new_rate != priv->rate) { + priv->rate = new_rate; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_BITRATE); + } +} + +static gboolean +periodic_update_cb (gpointer user_data) +{ + periodic_update (NM_DEVICE_WIFI (user_data), NULL); + return TRUE; +} + +static gboolean +bring_up (NMDevice *device, gboolean *no_firmware) +{ + if (!NM_DEVICE_WIFI_GET_PRIVATE (device)->enabled) + return FALSE; + + return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->bring_up (device, no_firmware); +} + +static void +emit_ap_added_removed (NMDeviceWifi *self, + guint signum, + NMAccessPoint *ap, + gboolean recheck_available_connections) +{ + g_signal_emit (self, signals[signum], 0, ap); + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_ACCESS_POINTS); + nm_device_emit_recheck_auto_activate (NM_DEVICE (self)); + if (recheck_available_connections) + nm_device_recheck_available_connections (NM_DEVICE (self)); +} + +static void +remove_access_point (NMDeviceWifi *device, + NMAccessPoint *ap) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + g_return_if_fail (ap); + g_return_if_fail (ap != priv->current_ap); + g_return_if_fail (g_slist_find (priv->ap_list, ap)); + + priv->ap_list = g_slist_remove (priv->ap_list, ap); + emit_ap_added_removed (self, ACCESS_POINT_REMOVED, ap, FALSE); + g_object_unref (ap); +} + +static void +remove_all_aps (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->ap_list) { + set_current_ap (self, NULL, FALSE, FALSE); + + while (priv->ap_list) + remove_access_point (self, NM_AP (priv->ap_list->data)); + + nm_device_recheck_available_connections (NM_DEVICE (self)); + } +} + +static void +deactivate (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + int ifindex = nm_device_get_ifindex (dev); + NMConnection *connection; + NM80211Mode old_mode = priv->mode; + + connection = nm_device_get_connection (dev); + if (connection) { + /* Clear wireless secrets tries when deactivating */ + g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL); + } + + if (priv->periodic_source_id) { + g_source_remove (priv->periodic_source_id); + priv->periodic_source_id = 0; + } + + cleanup_association_attempt (self, TRUE); + + priv->rate = 0; + + /* If the AP is 'fake', i.e. it wasn't actually found from + * a scan but the user tried to connect to it manually (maybe it + * was non-broadcasting or something) get rid of it, because 'fake' + * APs should only live for as long as we're connected to them. + **/ + set_current_ap (self, NULL, TRUE, FALSE); + + /* Clear any critical protocol notification in the Wi-Fi stack */ + nm_platform_wifi_indicate_addressing_running (ifindex, FALSE); + + /* Reset MAC address back to initial address */ + nm_device_set_hw_addr (dev, priv->initial_hw_addr, "reset", LOGD_WIFI); + + /* Ensure we're in infrastructure mode after deactivation; some devices + * (usually older ones) don't scan well in adhoc mode. + */ + if (nm_platform_wifi_get_mode (ifindex) != NM_802_11_MODE_INFRA) { + nm_device_take_down (NM_DEVICE (self), TRUE); + nm_platform_wifi_set_mode (ifindex, NM_802_11_MODE_INFRA); + nm_device_bring_up (NM_DEVICE (self), TRUE, NULL); + } + + if (priv->mode != NM_802_11_MODE_INFRA) { + priv->mode = NM_802_11_MODE_INFRA; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_MODE); + } + + /* Ensure we trigger a scan after deactivating a Hotspot */ + if (old_mode == NM_802_11_MODE_AP) { + cancel_pending_scan (self); + request_wireless_scan (self); + } +} + +static gboolean +is_adhoc_wpa (NMConnection *connection) +{ + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + const char *mode, *key_mgmt; + + /* The kernel doesn't support Ad-Hoc WPA connections well at this time, + * and turns them into open networks. It's been this way since at least + * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks. + */ + + s_wifi = nm_connection_get_setting_wireless (connection); + g_return_val_if_fail (s_wifi != NULL, FALSE); + + mode = nm_setting_wireless_get_mode (s_wifi); + if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) != 0) + return FALSE; + + s_wsec = nm_connection_get_setting_wireless_security (connection); + if (!s_wsec) + return FALSE; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + if (g_strcmp0 (key_mgmt, "wpa-none") != 0) + return FALSE; + + return TRUE; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSettingConnection *s_con; + NMSettingWireless *s_wireless; + const GByteArray *mac; + const GSList *mac_blacklist, *mac_blacklist_iter; + const char *mode; + + if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME)) + return FALSE; + + s_wireless = nm_connection_get_setting_wireless (connection); + if (!s_wireless) + return FALSE; + + mac = nm_setting_wireless_get_mac_address (s_wireless); + if (mac && memcmp (mac->data, &priv->perm_hw_addr, ETH_ALEN)) + return FALSE; + + /* Check for MAC address blacklist */ + mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless); + for (mac_blacklist_iter = mac_blacklist; mac_blacklist_iter; + mac_blacklist_iter = g_slist_next (mac_blacklist_iter)) { + struct ether_addr addr; + + if (!ether_aton_r (mac_blacklist_iter->data, &addr)) { + g_warn_if_reached (); + continue; + } + + if (memcmp (&addr, &priv->perm_hw_addr, ETH_ALEN) == 0) + return FALSE; + } + + if (is_adhoc_wpa (connection)) + return FALSE; + + /* Early exit if supplicant or device doesn't support requested mode */ + mode = nm_setting_wireless_get_mode (s_wireless); + if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) + return FALSE; + } else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) + return FALSE; + + if (priv->sup_iface) { + if (nm_supplicant_interface_get_ap_support (priv->sup_iface) == AP_SUPPORT_NO) + return FALSE; + } + } + + // FIXME: check channel/freq/band against bands the hardware supports + // FIXME: check encryption against device capabilities + // FIXME: check bitrate against device capabilities + + return TRUE; +} + + +static gboolean +_internal_check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object, + gboolean ignore_ap_list) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device); + NMSettingWireless *s_wifi; + const char *mode; + GSList *ap_iter = NULL; + + s_wifi = nm_connection_get_setting_wireless (connection); + g_return_val_if_fail (s_wifi, FALSE); + + if (specific_object) { + NMAccessPoint *ap; + + ap = get_ap_by_path (NM_DEVICE_WIFI (device), specific_object); + return ap ? nm_ap_check_compatible (ap, connection) : FALSE; + } + + /* Ad-Hoc and AP connections are always available because they may be + * started at any time. + */ + mode = nm_setting_wireless_get_mode (s_wifi); + if ( g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0 + || g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0) + return TRUE; + + /* Hidden SSIDs obviously don't always appear in the scan list either */ + if (nm_setting_wireless_get_hidden (s_wifi) || ignore_ap_list) + return TRUE; + + /* check if its visible */ + for (ap_iter = priv->ap_list; ap_iter; ap_iter = g_slist_next (ap_iter)) { + if (nm_ap_check_compatible (NM_AP (ap_iter->data), connection)) + return TRUE; + } + + return FALSE; +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + return _internal_check_connection_available (device, connection, specific_object, FALSE); +} + +/* FIXME: remove this function when we require the 'hidden' property to be + * set before a hidden connection can be activated. + */ +static gboolean +check_connection_available_wifi_hidden (NMDevice *device, + NMConnection *connection) +{ + return _internal_check_connection_available (device, connection, NULL, TRUE); +} + +/* + * List of manufacturer default SSIDs that are often unchanged by users. + * + * NOTE: this list should *not* contain networks that you would like to + * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot". + */ +static const char * +manf_defaults[] = { + "linksys", + "linksys-a", + "linksys-g", + "default", + "belkin54g", + "NETGEAR", + "o2DSL", + "WLAN", + "ALICE-WLAN", + "Speedport W 501V", +}; + +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0])) + +static gboolean +is_manf_default_ssid (const GByteArray *ssid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE (manf_defaults); i++) { + if (ssid->len == strlen (manf_defaults[i])) { + if (memcmp (manf_defaults[i], ssid->data, ssid->len) == 0) + return TRUE; + } + } + return FALSE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x *s_8021x; + const GByteArray *setting_mac; + char *format, *str_ssid = NULL; + NMAccessPoint *ap = NULL; + const GByteArray *ssid = NULL; + GSList *iter; + gboolean hidden = FALSE; + + s_wifi = nm_connection_get_setting_wireless (connection); + s_wsec = nm_connection_get_setting_wireless_security (connection); + s_8021x = nm_connection_get_setting_802_1x (connection); + + if (!specific_object) { + /* If not given a specific object, we need at minimum an SSID */ + if (!s_wifi) { + g_set_error_literal (error, + NM_WIFI_ERROR, + NM_WIFI_ERROR_CONNECTION_INVALID, + "A 'wireless' setting is required if no AP path was given."); + return FALSE; + } + + ssid = nm_setting_wireless_get_ssid (s_wifi); + if (!ssid || !ssid->len) { + g_set_error_literal (error, + NM_WIFI_ERROR, + NM_WIFI_ERROR_CONNECTION_INVALID, + "A 'wireless' setting with a valid SSID is required if no AP path was given."); + return FALSE; + } + + /* Find a compatible AP in the scan list */ + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) { + if (nm_ap_check_compatible (NM_AP (iter->data), connection)) { + ap = NM_AP (iter->data); + break; + } + } + + /* If we still don't have an AP, then the WiFI settings needs to be + * fully specified by the client. Might not be able to find an AP + * if the network isn't broadcasting the SSID for example. + */ + if (!ap) { + GSList *settings = NULL; + gboolean valid; + + settings = g_slist_prepend (settings, s_wifi); + if (s_wsec) + settings = g_slist_prepend (settings, s_wsec); + if (s_8021x) + settings = g_slist_prepend (settings, s_8021x); + valid = nm_setting_verify (NM_SETTING (s_wifi), settings, error); + g_slist_free (settings); + if (!valid) + return FALSE; + + hidden = TRUE; + } + } else { + ap = get_ap_by_path (self, specific_object); + if (!ap) { + g_set_error (error, + NM_WIFI_ERROR, + NM_WIFI_ERROR_ACCESS_POINT_NOT_FOUND, + "The access point %s was not in the scan list.", + specific_object); + return FALSE; + } + } + + /* Add a wifi setting if one doesn't exist yet */ + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + } + + if (ap) { + ssid = nm_ap_get_ssid (ap); + + if (ssid == NULL) { + /* The AP must be hidden. Connecting to a WiFi AP requires the SSID + * as part of the initial handshake, so check the connection details + * for the SSID. The AP object will still be used for encryption + * settings and such. + */ + ssid = nm_setting_wireless_get_ssid (s_wifi); + } + + if (ssid == NULL) { + /* If there's no SSID on the AP itself, and no SSID in the + * connection data, then we cannot connect at all. Return an error. + */ + g_set_error_literal (error, + NM_WIFI_ERROR, + NM_WIFI_ERROR_CONNECTION_INVALID, + "A 'wireless' setting with a valid SSID is required for hidden access points."); + return FALSE; + } + + /* If the SSID is a well-known SSID, lock the connection to the AP's + * specific BSSID so NM doesn't autoconnect to some random wifi net. + */ + if (!nm_ap_complete_connection (ap, + connection, + is_manf_default_ssid (ssid), + error)) + return FALSE; + } + + /* The kernel doesn't support Ad-Hoc WPA connections well at this time, + * and turns them into open networks. It's been this way since at least + * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks. + */ + if (is_adhoc_wpa (connection)) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_ERROR, + NM_SETTING_WIRELESS_ERROR_INVALID_PROPERTY, + "WPA Ad-Hoc disabled due to kernel bugs"); + return FALSE; + } + + g_assert (ssid); + str_ssid = nm_utils_ssid_to_utf8 (ssid); + format = g_strdup_printf ("%s %%d", str_ssid); + + nm_utils_complete_generic (connection, + NM_SETTING_WIRELESS_SETTING_NAME, + existing_connections, + format, + str_ssid, + TRUE); + g_free (str_ssid); + g_free (format); + + if (hidden) + g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL); + + setting_mac = nm_setting_wireless_get_mac_address (s_wifi); + if (setting_mac) { + /* Make sure the setting MAC (if any) matches the device's permanent MAC */ + if (memcmp (setting_mac->data, priv->perm_hw_addr, ETH_ALEN)) { + g_set_error (error, + NM_SETTING_WIRELESS_ERROR, + NM_SETTING_WIRELESS_ERROR_INVALID_PROPERTY, + NM_SETTING_WIRELESS_MAC_ADDRESS); + return FALSE; + } + } else { + GByteArray *mac; + const guint8 null_mac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + + /* Lock the connection to this device by default if it uses a + * permanent MAC address (ie not a 'locally administered' one) + */ + if ( !(priv->perm_hw_addr[0] & 0x02) + && memcmp (priv->perm_hw_addr, null_mac, ETH_ALEN)) { + mac = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (mac, priv->perm_hw_addr, ETH_ALEN); + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_MAC_ADDRESS, mac, NULL); + g_byte_array_free (mac, TRUE); + } + } + + return TRUE; +} + +static gboolean +is_available (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSupplicantInterface *sup_iface; + guint32 state; + + if (!priv->enabled) { + nm_log_dbg (LOGD_WIFI, "(%s): not available because not enabled", + nm_device_get_iface (dev)); + return FALSE; + } + + sup_iface = priv->sup_iface; + if (!sup_iface) { + nm_log_dbg (LOGD_WIFI, "(%s): not available because supplicant not running", + nm_device_get_iface (dev)); + return FALSE; + } + + state = nm_supplicant_interface_get_state (sup_iface); + if ( state < NM_SUPPLICANT_INTERFACE_STATE_READY + || state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) { + nm_log_dbg (LOGD_WIFI, "(%s): not available because supplicant interface not ready", + nm_device_get_iface (dev)); + return FALSE; + } + + return TRUE; +} + +static gboolean +can_auto_connect (NMDevice *dev, + NMConnection *connection, + char **specific_object) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList *ap_iter; + const char *method = NULL; + guint64 timestamp = 0; + + if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->can_auto_connect (dev, connection, specific_object)) + return FALSE; + + /* Don't autoconnect to networks that have been tried at least once + * but haven't been successful, since these are often accidental choices + * from the menu and the user may not know the password. + */ + if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), ×tamp)) { + if (timestamp == 0) + return FALSE; + } + + /* Use the connection if it's a shared connection */ + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + return TRUE; + + for (ap_iter = priv->ap_list; ap_iter; ap_iter = g_slist_next (ap_iter)) { + NMAccessPoint *ap = NM_AP (ap_iter->data); + + if (nm_ap_check_compatible (ap, connection)) { + /* All good; connection is usable */ + *specific_object = (char *) nm_ap_get_dbus_path (ap); + return TRUE; + } + } + + return FALSE; +} + +static void +ap_list_dump (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList * elt; + int i = 0; + + g_return_if_fail (NM_IS_DEVICE_WIFI (self)); + + nm_log_dbg (LOGD_WIFI_SCAN, "Current AP list:"); + for (elt = priv->ap_list; elt; elt = g_slist_next (elt), i++) { + NMAccessPoint * ap = NM_AP (elt->data); + nm_ap_dump (ap, "List AP: "); + } + nm_log_dbg (LOGD_WIFI_SCAN, "Current AP list: done"); +} + +static gboolean +impl_device_get_access_points (NMDeviceWifi *self, + GPtrArray **aps, + GError **err) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList *elt; + + *aps = g_ptr_array_new (); + for (elt = priv->ap_list; elt; elt = g_slist_next (elt)) { + NMAccessPoint *ap = NM_AP (elt->data); + + if (nm_ap_get_ssid (ap)) + g_ptr_array_add (*aps, g_strdup (nm_ap_get_dbus_path (ap))); + } + return TRUE; +} + +static gboolean +impl_device_get_all_access_points (NMDeviceWifi *self, + GPtrArray **aps, + GError **err) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList *elt; + + *aps = g_ptr_array_new (); + for (elt = priv->ap_list; elt; elt = g_slist_next (elt)) + g_ptr_array_add (*aps, g_strdup (nm_ap_get_dbus_path (NM_AP (elt->data)))); + return TRUE; +} + +static void +request_scan_cb (NMDevice *device, + DBusGMethodInvocation *context, + GError *error, + gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + GError *local = NULL; + + if (error) { + dbus_g_method_return_error (context, error); + return; + } + + if (!check_scanning_allowed (self)) { + local = g_error_new_literal (NM_WIFI_ERROR, + NM_WIFI_ERROR_SCAN_NOT_ALLOWED, + "Scanning not allowed at this time"); + dbus_g_method_return_error (context, local); + g_error_free (local); + return; + } + + cancel_pending_scan (self); + request_wireless_scan (self); + dbus_g_method_return (context); +} + +static void +impl_device_request_scan (NMDeviceWifi *self, + GHashTable *options, + DBusGMethodInvocation *context) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMDevice *device = NM_DEVICE (self); + gint32 last_scan; + GError *error; + + if ( !priv->enabled + || !priv->sup_iface + || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED + || nm_device_is_activating (device)) { + error = g_error_new_literal (NM_WIFI_ERROR, + NM_WIFI_ERROR_SCAN_NOT_ALLOWED, + "Scanning not allowed while unavailable or activating"); + goto error; + } + + if (nm_supplicant_interface_get_scanning (priv->sup_iface)) { + error = g_error_new_literal (NM_WIFI_ERROR, + NM_WIFI_ERROR_SCAN_NOT_ALLOWED, + "Scanning not allowed while already scanning"); + goto error; + } + + last_scan = nm_supplicant_interface_get_last_scan_time (priv->sup_iface); + if (last_scan && (nm_utils_get_monotonic_timestamp_s () - last_scan) < 10) { + error = g_error_new_literal (NM_WIFI_ERROR, + NM_WIFI_ERROR_SCAN_NOT_ALLOWED, + "Scanning not allowed immediately following previous scan"); + goto error; + } + + /* Ask the manager to authenticate this request for us */ + g_signal_emit_by_name (device, + NM_DEVICE_AUTH_REQUEST, + context, + NULL, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + request_scan_cb, + NULL); + return; + +error: + dbus_g_method_return_error (context, error); + g_error_free (error); +} + +static gboolean +scanning_allowed (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + guint32 sup_state; + NMConnection *connection; + + g_return_val_if_fail (priv->sup_iface != NULL, FALSE); + + /* Scanning not done in AP mode */ + if (priv->mode == NM_802_11_MODE_AP) + return FALSE; + + switch (nm_device_get_state (NM_DEVICE (self))) { + case NM_DEVICE_STATE_UNKNOWN: + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + case NM_DEVICE_STATE_DEACTIVATING: + /* Don't scan when unusable or activating */ + return FALSE; + case NM_DEVICE_STATE_DISCONNECTED: + case NM_DEVICE_STATE_FAILED: + /* Can always scan when disconnected */ + return TRUE; + case NM_DEVICE_STATE_ACTIVATED: + /* Need to do further checks when activated */ + break; + } + + /* Don't scan if the supplicant is busy */ + sup_state = nm_supplicant_interface_get_state (priv->sup_iface); + if ( sup_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING + || sup_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED + || sup_state == NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE + || sup_state == NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE + || nm_supplicant_interface_get_scanning (priv->sup_iface)) + return FALSE; + + connection = nm_device_get_connection (NM_DEVICE (self)); + if (connection) { + NMSettingWireless *s_wifi; + const char *ip4_method = NULL; + const GByteArray *bssid; + + /* Don't scan when a shared connection is active; it makes drivers mad */ + ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + + if (!strcmp (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + return FALSE; + + /* Don't scan when the connection is locked to a specifc AP, since + * intra-ESS roaming (which requires periodic scanning) isn't being + * used due to the specific AP lock. (bgo #513820) + */ + s_wifi = nm_connection_get_setting_wireless (connection); + g_assert (s_wifi); + bssid = nm_setting_wireless_get_bssid (s_wifi); + if (bssid && bssid->len == ETH_ALEN) + return FALSE; + } + + return TRUE; +} + +static gboolean +scanning_allowed_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer data) +{ + if (!g_value_get_boolean (handler_return)) + g_value_set_boolean (return_accu, FALSE); + return TRUE; +} + +static gboolean +check_scanning_allowed (NMDeviceWifi *self) +{ + GValue instance = G_VALUE_INIT; + GValue retval = G_VALUE_INIT; + + g_value_init (&instance, G_TYPE_OBJECT); + g_value_take_object (&instance, self); + + g_value_init (&retval, G_TYPE_BOOLEAN); + g_value_set_boolean (&retval, TRUE); + + /* Use g_signal_emitv() rather than g_signal_emit() to avoid the return + * value being changed if no handlers are connected */ + g_signal_emitv (&instance, signals[SCANNING_ALLOWED], 0, &retval); + + return g_value_get_boolean (&retval); +} + +static gboolean +hidden_filter_func (NMConnectionProvider *provider, + NMConnection *connection, + gpointer user_data) +{ + NMSettingWireless *s_wifi; + + s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (connection); + return s_wifi ? nm_setting_wireless_get_hidden (s_wifi) : FALSE; +} + +static GPtrArray * +build_hidden_probe_list (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + guint max_scan_ssids = nm_supplicant_interface_get_max_scan_ssids (priv->sup_iface); + GSList *connections, *iter; + GPtrArray *ssids = NULL; + static GByteArray *nullssid = NULL; + + /* Need at least two: wildcard SSID and one or more hidden SSIDs */ + if (max_scan_ssids < 2) + return NULL; + + /* Static wildcard SSID used for every scan */ + if (G_UNLIKELY (nullssid == NULL)) + nullssid = g_byte_array_new (); + + connections = nm_connection_provider_get_best_connections (nm_connection_provider_get (), + max_scan_ssids - 1, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL, + hidden_filter_func, + NULL); + if (connections && connections->data) { + ssids = g_ptr_array_sized_new (max_scan_ssids - 1); + g_ptr_array_add (ssids, nullssid); /* Add wildcard SSID */ + } + + for (iter = connections; iter; iter = g_slist_next (iter)) { + NMConnection *connection = iter->data; + NMSettingWireless *s_wifi; + const GByteArray *ssid; + + s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (connection); + g_assert (s_wifi); + ssid = nm_setting_wireless_get_ssid (s_wifi); + g_assert (ssid); + g_ptr_array_add (ssids, (gpointer) ssid); + } + g_slist_free (connections); + + return ssids; +} + +static gboolean +request_wireless_scan (gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + gboolean backoff = FALSE; + GPtrArray *ssids = NULL; + + if (priv->requested_scan) { + /* There's already a scan in progress */ + return FALSE; + } + + if (check_scanning_allowed (self)) { + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scanning requested", + nm_device_get_iface (NM_DEVICE (self))); + + ssids = build_hidden_probe_list (self); + + if (nm_logging_enabled (LOGL_DEBUG, LOGD_WIFI_SCAN)) { + if (ssids) { + guint i; + char *foo; + + for (i = 0; i < ssids->len; i++) { + foo = nm_utils_ssid_to_utf8 (g_ptr_array_index (ssids, i)); + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): (%d) probe scanning SSID '%s'", + nm_device_get_iface (NM_DEVICE (self)), + i, foo ? foo : "<hidden>"); + g_free (foo); + } + } else { + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): no SSIDs to probe scan", + nm_device_get_iface (NM_DEVICE (self))); + } + } + + if (nm_supplicant_interface_request_scan (priv->sup_iface, ssids)) { + /* success */ + backoff = TRUE; + priv->requested_scan = TRUE; + nm_device_add_pending_action (NM_DEVICE (self), "scan", TRUE); + } + + if (ssids) { + /* Elements owned by the connections, so we don't free them here */ + g_ptr_array_free (ssids, TRUE); + } + } else { + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scan requested but not allowed at this time", + nm_device_get_iface (NM_DEVICE (self))); + } + + priv->pending_scan_id = 0; + schedule_scan (self, backoff); + return FALSE; +} + + +/* + * schedule_scan + * + * Schedule a wireless scan. + * + */ +static void +schedule_scan (NMDeviceWifi *self, gboolean backoff) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + gint32 now = nm_utils_get_monotonic_timestamp_s (); + + /* Cancel the pending scan if it would happen later than (now + the scan_interval) */ + if (priv->pending_scan_id) { + if (now + priv->scan_interval < priv->scheduled_scan_time) + cancel_pending_scan (self); + } + + if (!priv->pending_scan_id) { + guint factor = 2, next_scan = priv->scan_interval; + + if ( nm_device_is_activating (NM_DEVICE (self)) + || (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED)) + factor = 1; + + priv->pending_scan_id = g_timeout_add_seconds (next_scan, + request_wireless_scan, + self); + + priv->scheduled_scan_time = now + priv->scan_interval; + if (backoff && (priv->scan_interval < (SCAN_INTERVAL_MAX / factor))) { + priv->scan_interval += (SCAN_INTERVAL_STEP / factor); + /* Ensure the scan interval will never be less than 20s... */ + priv->scan_interval = MAX(priv->scan_interval, SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP); + /* ... or more than 120s */ + priv->scan_interval = MIN(priv->scan_interval, SCAN_INTERVAL_MAX); + } else if (!backoff && (priv->scan_interval == 0)) { + /* Invalid combination; would cause continual rescheduling of + * the scan and hog CPU. Reset to something minimally sane. + */ + priv->scan_interval = 5; + } + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scheduled scan in %d seconds (interval now %d seconds)", + nm_device_get_iface (NM_DEVICE (self)), + next_scan, + priv->scan_interval); + + } +} + + +static void +cancel_pending_scan (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->pending_scan_id) { + g_source_remove (priv->pending_scan_id); + priv->pending_scan_id = 0; + } +} + +static void +supplicant_iface_scan_done_cb (NMSupplicantInterface *iface, + gboolean success, + NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scan %s", + nm_device_get_iface (NM_DEVICE (self)), + success ? "successful" : "failed"); + + schedule_scan (self, success); + + /* Ensure that old APs get removed, which otherwise only + * happens when there are new BSSes. + */ + schedule_scanlist_cull (self); + + if (priv->requested_scan) { + priv->requested_scan = FALSE; + nm_device_remove_pending_action (NM_DEVICE (self), "scan", TRUE); + } +} + +/**************************************************************************** + * WPA Supplicant control stuff + * + */ + +static void +try_fill_ssid_for_hidden_ap (NMAccessPoint *ap) +{ + const struct ether_addr *bssid; + const GSList *connections, *iter; + + g_return_if_fail (nm_ap_get_ssid (ap) == NULL); + + bssid = nm_ap_get_address (ap); + g_assert (bssid); + + /* Look for this AP's BSSID in the seen-bssids list of a connection, + * and if a match is found, copy over the SSID */ + connections = nm_connection_provider_get_connections (nm_connection_provider_get ()); + for (iter = connections; iter; iter = g_slist_next (iter)) { + NMConnection *connection = NM_CONNECTION (iter->data); + NMSettingWireless *s_wifi; + + s_wifi = nm_connection_get_setting_wireless (connection); + if (s_wifi) { + if (nm_settings_connection_has_seen_bssid (NM_SETTINGS_CONNECTION (connection), bssid)) { + nm_ap_set_ssid (ap, nm_setting_wireless_get_ssid (s_wifi)); + break; + } + } + } +} + +#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" +#define MAC_ARG(x) ((guint8*)(x))[0],((guint8*)(x))[1],((guint8*)(x))[2],((guint8*)(x))[3],((guint8*)(x))[4],((guint8*)(x))[5] + +/* + * merge_scanned_ap + * + * If there is already an entry that matches the BSSID and ESSID of the + * AP to merge, replace that entry with the scanned AP. Otherwise, add + * the scanned AP to the list. + * + * TODO: possibly need to differentiate entries based on security too; i.e. if + * there are two scan results with the same BSSID and SSID but different + * security options? + * + */ +static void +merge_scanned_ap (NMDeviceWifi *self, + NMAccessPoint *merge_ap) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMAccessPoint *found_ap = NULL; + const GByteArray *ssid; + const struct ether_addr *bssid; + gboolean strict_match = TRUE; + + /* Let the manager try to fill in the SSID from seen-bssids lists */ + bssid = nm_ap_get_address (merge_ap); + ssid = nm_ap_get_ssid (merge_ap); + if (!ssid || nm_utils_is_empty_ssid (ssid->data, ssid->len)) { + /* Try to fill the SSID from the AP database */ + try_fill_ssid_for_hidden_ap (merge_ap); + + ssid = nm_ap_get_ssid (merge_ap); + if (ssid && (nm_utils_is_empty_ssid (ssid->data, ssid->len) == FALSE)) { + /* Yay, matched it, no longer treat as hidden */ + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): matched hidden AP " MAC_FMT " => '%s'", + nm_device_get_iface (NM_DEVICE (self)), + MAC_ARG (bssid->ether_addr_octet), + nm_utils_escape_ssid (ssid->data, ssid->len)); + nm_ap_set_broadcast (merge_ap, FALSE); + } else { + /* Didn't have an entry for this AP in the database */ + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): failed to match hidden AP " MAC_FMT, + nm_device_get_iface (NM_DEVICE (self)), + MAC_ARG (bssid->ether_addr_octet)); + } + } + + /* If the incoming scan result matches the hidden AP that NM is currently + * connected to but hasn't been seen in the scan list yet, don't use + * strict matching. Because the capabilities of the fake AP have to be + * constructed from the NMConnection of the activation request, they won't + * always be the same as the capabilities of the real AP from the scan. + */ + if (priv->current_ap && nm_ap_get_fake (priv->current_ap)) + strict_match = FALSE; + + found_ap = get_ap_by_supplicant_path (self, nm_ap_get_supplicant_path (merge_ap)); + if (!found_ap) + found_ap = nm_ap_match_in_list (merge_ap, priv->ap_list, strict_match); + if (found_ap) { + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): merging AP '%s' " MAC_FMT " (%p) with existing (%p)", + nm_device_get_iface (NM_DEVICE (self)), + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)", + MAC_ARG (bssid->ether_addr_octet), + merge_ap, + found_ap); + + nm_ap_set_supplicant_path (found_ap, nm_ap_get_supplicant_path (merge_ap)); + nm_ap_set_flags (found_ap, nm_ap_get_flags (merge_ap)); + nm_ap_set_wpa_flags (found_ap, nm_ap_get_wpa_flags (merge_ap)); + nm_ap_set_rsn_flags (found_ap, nm_ap_get_rsn_flags (merge_ap)); + nm_ap_set_strength (found_ap, nm_ap_get_strength (merge_ap)); + nm_ap_set_last_seen (found_ap, nm_ap_get_last_seen (merge_ap)); + nm_ap_set_broadcast (found_ap, nm_ap_get_broadcast (merge_ap)); + nm_ap_set_freq (found_ap, nm_ap_get_freq (merge_ap)); + nm_ap_set_max_bitrate (found_ap, nm_ap_get_max_bitrate (merge_ap)); + + /* If the AP is noticed in a scan, it's automatically no longer + * fake, since it clearly exists somewhere. + */ + nm_ap_set_fake (found_ap, FALSE); + } else { + /* New entry in the list */ + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): adding new AP '%s' " MAC_FMT " (%p)", + nm_device_get_iface (NM_DEVICE (self)), + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)", + MAC_ARG (bssid->ether_addr_octet), + merge_ap); + + g_object_ref (merge_ap); + priv->ap_list = g_slist_prepend (priv->ap_list, merge_ap); + nm_ap_export_to_dbus (merge_ap); + emit_ap_added_removed (self, ACCESS_POINT_ADDED, merge_ap, TRUE); + } +} + +#define WPAS_REMOVED_TAG "supplicant-removed" + +static gboolean +cull_scan_list (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + gint32 now = nm_utils_get_monotonic_timestamp_s (); + GSList *outdated_list = NULL; + GSList *elt; + guint32 removed = 0, total = 0; + + priv->scanlist_cull_id = 0; + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): checking scan list for outdated APs", + nm_device_get_iface (NM_DEVICE (self))); + + /* Walk the access point list and remove any access points older than + * three times the inactive scan interval. + */ + for (elt = priv->ap_list; elt; elt = g_slist_next (elt), total++) { + NMAccessPoint *ap = elt->data; + const guint prune_interval_s = SCAN_INTERVAL_MAX * 3; + gint32 last_seen; + + /* Don't cull the associated AP or manually created APs */ + if (ap == priv->current_ap) + continue; + g_assert (!nm_ap_get_fake (ap)); /* only the current_ap can be fake */ + + /* Don't cull APs still known to the supplicant. Since the supplicant + * doesn't yet emit property updates for "last seen" we have to rely + * on changing signal strength for updating "last seen". But if the + * AP's strength doesn't change we won't get any updates for the AP, + * and we'll end up here even if the AP was still found by the + * supplicant in the last scan. + */ + if ( nm_ap_get_supplicant_path (ap) + && g_object_get_data (G_OBJECT (ap), WPAS_REMOVED_TAG) == NULL) + continue; + + last_seen = nm_ap_get_last_seen (ap); + if (!last_seen || last_seen + prune_interval_s < now) + outdated_list = g_slist_prepend (outdated_list, ap); + } + + /* Remove outdated APs */ + for (elt = outdated_list; elt; elt = g_slist_next (elt)) { + NMAccessPoint *outdated_ap = NM_AP (elt->data); + const struct ether_addr *bssid; + const GByteArray *ssid; + + bssid = nm_ap_get_address (outdated_ap); + ssid = nm_ap_get_ssid (outdated_ap); + nm_log_dbg (LOGD_WIFI_SCAN, + " removing %02x:%02x:%02x:%02x:%02x:%02x (%s%s%s)", + bssid->ether_addr_octet[0], bssid->ether_addr_octet[1], + bssid->ether_addr_octet[2], bssid->ether_addr_octet[3], + bssid->ether_addr_octet[4], bssid->ether_addr_octet[5], + ssid ? "'" : "", + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)", + ssid ? "'" : ""); + + remove_access_point (self, outdated_ap); + removed++; + } + g_slist_free (outdated_list); + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): removed %d APs (of %d)", + nm_device_get_iface (NM_DEVICE (self)), + removed, total); + + ap_list_dump (self); + + if(removed > 0) + nm_device_recheck_available_connections (NM_DEVICE (self)); + + return FALSE; +} + +static void +schedule_scanlist_cull (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + /* Cull the scan list after the last request for it has come in */ + if (priv->scanlist_cull_id) + g_source_remove (priv->scanlist_cull_id); + priv->scanlist_cull_id = g_timeout_add_seconds (4, (GSourceFunc) cull_scan_list, self); +} + +static void +supplicant_iface_new_bss_cb (NMSupplicantInterface *iface, + const char *object_path, + GHashTable *properties, + NMDeviceWifi *self) +{ + NMDeviceState state; + NMAccessPoint *ap; + + g_return_if_fail (self != NULL); + g_return_if_fail (properties != NULL); + g_return_if_fail (iface != NULL); + + /* Ignore new APs when unavailable, unmanaged, or in AP mode */ + state = nm_device_get_state (NM_DEVICE (self)); + if (state <= NM_DEVICE_STATE_UNAVAILABLE) + return; + if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP) + return; + + ap = nm_ap_new_from_properties (object_path, properties); + if (ap) { + nm_ap_dump (ap, "New AP: "); + + /* Add the AP to the device's AP list */ + merge_scanned_ap (self, ap); + g_object_unref (ap); + } else { + nm_log_warn (LOGD_WIFI_SCAN, "(%s): invalid AP properties received", + nm_device_get_iface (NM_DEVICE (self))); + } + + /* Remove outdated access points */ + schedule_scanlist_cull (self); +} + +static void +supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface, + const char *object_path, + GHashTable *properties, + NMDeviceWifi *self) +{ + NMDeviceState state; + NMAccessPoint *ap; + + g_return_if_fail (self != NULL); + g_return_if_fail (object_path != NULL); + g_return_if_fail (properties != NULL); + + /* Ignore new APs when unavailable or unamnaged */ + state = nm_device_get_state (NM_DEVICE (self)); + if (state <= NM_DEVICE_STATE_UNAVAILABLE) + return; + + /* Update the AP's last-seen property */ + ap = get_ap_by_supplicant_path (self, object_path); + if (ap) + nm_ap_set_last_seen (ap, nm_utils_get_monotonic_timestamp_s ()); + + /* Remove outdated access points */ + schedule_scanlist_cull (self); +} + +static void +supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface, + const char *object_path, + NMDeviceWifi *self) +{ + NMAccessPoint *ap; + + g_return_if_fail (self != NULL); + g_return_if_fail (object_path != NULL); + + ap = get_ap_by_supplicant_path (self, object_path); + if (ap) + g_object_set_data (G_OBJECT (ap), WPAS_REMOVED_TAG, GUINT_TO_POINTER (TRUE)); +} + +static void +remove_supplicant_timeouts (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->sup_timeout_id) { + g_source_remove (priv->sup_timeout_id); + priv->sup_timeout_id = 0; + } + + if (priv->link_timeout_id) { + g_source_remove (priv->link_timeout_id); + priv->link_timeout_id = 0; + } +} + +static void +cleanup_association_attempt (NMDeviceWifi *self, gboolean disconnect) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + remove_supplicant_interface_error_handler (self); + remove_supplicant_timeouts (self); + if (disconnect && priv->sup_iface) + nm_supplicant_interface_disconnect (priv->sup_iface); +} + +static void +wifi_secrets_cb (NMActRequest *req, + guint32 call_id, + NMConnection *connection, + GError *error, + gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + + g_return_if_fail (req == nm_device_get_act_request (dev)); + g_return_if_fail (nm_device_get_state (dev) == NM_DEVICE_STATE_NEED_AUTH); + g_return_if_fail (nm_act_request_get_connection (req) == connection); + + if (error) { + nm_log_warn (LOGD_WIFI, "%s", error->message); + nm_device_state_changed (dev, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } else + nm_device_activate_schedule_stage1_device_prepare (dev); +} + +/* + * link_timeout_cb + * + * Called when the link to the access point has been down for a specified + * period of time. + */ +static gboolean +link_timeout_cb (gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + nm_log_warn (LOGD_WIFI, "(%s): link timed out.", nm_device_get_iface (dev)); + + priv->link_timeout_id = 0; + + /* Disconnect event while activated; the supplicant hasn't been able + * to reassociate within the timeout period, so the connection must + * fail. + */ + if (nm_device_get_state (dev) != NM_DEVICE_STATE_ACTIVATED) + return FALSE; + + /* If the access point failed, and wasn't found by the supplicant when it + * attempted to reconnect, then it's probably out of range or turned off. + * Remove it from the list and if it's actually still present, it'll be + * found in the next scan. + */ + if (priv->ssid_found == FALSE && priv->current_ap) + set_current_ap (self, NULL, TRUE, TRUE); + + nm_device_state_changed (dev, + NM_DEVICE_STATE_FAILED, + priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT : + NM_DEVICE_STATE_REASON_SSID_NOT_FOUND); + return FALSE; +} + +static gboolean +need_new_8021x_secrets (NMDeviceWifi *self, + guint32 old_state, + const char **setting_name) +{ + NMSetting8021x *s_8021x; + NMSettingWirelessSecurity *s_wsec; + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + NMConnection *connection; + + g_assert (setting_name != NULL); + + connection = nm_device_get_connection (NM_DEVICE (self)); + g_return_val_if_fail (connection != NULL, FALSE); + + /* 802.1x stuff only happens in the supplicant's ASSOCIATED state when it's + * attempting to authenticate with the AP. + */ + if (old_state != NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED) + return FALSE; + + /* If it's an 802.1x or LEAP connection with "always ask"/unsaved secrets + * then we need to ask again because it might be an OTP token and the PIN + * may have changed. + */ + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x) { + nm_setting_get_secret_flags (NM_SETTING (s_8021x), + NM_SETTING_802_1X_PASSWORD, + &secret_flags, + NULL); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + *setting_name = NM_SETTING_802_1X_SETTING_NAME; + return *setting_name ? TRUE : FALSE; + } + + s_wsec = nm_connection_get_setting_wireless_security (connection); + if (s_wsec) { + nm_setting_get_secret_flags (NM_SETTING (s_wsec), + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + &secret_flags, + NULL); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + return *setting_name ? TRUE : FALSE; + } + + /* Not a LEAP or 802.1x connection */ + return FALSE; +} + +static gboolean +need_new_wpa_psk (NMDeviceWifi *self, + guint32 old_state, + const char **setting_name) +{ + NMSettingWirelessSecurity *s_wsec; + NMConnection *connection; + const char *key_mgmt = NULL; + + g_assert (setting_name != NULL); + + connection = nm_device_get_connection (NM_DEVICE (self)); + g_return_val_if_fail (connection != NULL, FALSE); + + /* A bad PSK will cause the supplicant to disconnect during the 4-way handshake */ + if (old_state != NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE) + return FALSE; + + s_wsec = nm_connection_get_setting_wireless_security (connection); + if (s_wsec) + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + + if (g_strcmp0 (key_mgmt, "wpa-psk") == 0) { + *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + return TRUE; + } + + /* Not a WPA-PSK connection */ + return FALSE; +} + +static gboolean +handle_8021x_or_psk_auth_fail (NMDeviceWifi *self, + guint32 new_state, + guint32 old_state, + int disconnect_reason) +{ + NMDevice *device = NM_DEVICE (self); + NMActRequest *req; + NMConnection *connection; + const char *setting_name = NULL; + gboolean handled = FALSE; + + g_return_val_if_fail (new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE); + + req = nm_device_get_act_request (NM_DEVICE (self)); + g_return_val_if_fail (req != NULL, FALSE); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + if ( need_new_8021x_secrets (self, old_state, &setting_name) + || need_new_wpa_psk (self, old_state, &setting_name)) { + + nm_connection_clear_secrets (connection); + + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): disconnected during association," + " asking for new key.", nm_device_get_iface (device)); + + cleanup_association_attempt (self, TRUE); + nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + nm_act_request_get_secrets (req, + setting_name, + NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION + | NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW, + NULL, + wifi_secrets_cb, + self); + handled = TRUE; + } + + return handled; +} + +static void +supplicant_iface_state_cb (NMSupplicantInterface *iface, + guint32 new_state, + guint32 old_state, + int disconnect_reason, + gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMDevice *device = NM_DEVICE (self); + NMDeviceState devstate; + gboolean scanning; + + if (new_state == old_state) + return; + + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "(%s): supplicant interface state: %s -> %s", + nm_device_get_iface (device), + nm_supplicant_interface_state_to_string (old_state), + nm_supplicant_interface_state_to_string (new_state)); + + devstate = nm_device_get_state (device); + scanning = nm_supplicant_interface_get_scanning (iface); + + /* In these states we know the supplicant is actually talking to something */ + if ( new_state >= NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING + && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) + priv->ssid_found = TRUE; + + switch (new_state) { + case NM_SUPPLICANT_INTERFACE_STATE_READY: + priv->scan_interval = SCAN_INTERVAL_MIN; + + /* If the interface can now be activated because the supplicant is now + * available, transition to DISCONNECTED. + */ + if ((devstate == NM_DEVICE_STATE_UNAVAILABLE) && nm_device_is_available (device)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE); + } + + nm_log_dbg (LOGD_WIFI_SCAN, + "(%s): supplicant ready, requesting initial scan", + nm_device_get_iface (device)); + + /* Request a scan to get latest results */ + cancel_pending_scan (self); + request_wireless_scan (self); + + if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY) + nm_device_remove_pending_action (device, "waiting for supplicant", TRUE); + break; + case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED: + remove_supplicant_interface_error_handler (self); + remove_supplicant_timeouts (self); + + /* If this is the initial association during device activation, + * schedule the next activation stage. + */ + if (devstate == NM_DEVICE_STATE_CONFIG) { + NMConnection *connection; + NMSettingWireless *s_wifi; + const GByteArray *ssid; + + connection = nm_device_get_connection (NM_DEVICE (self)); + g_return_if_fail (connection); + + s_wifi = nm_connection_get_setting_wireless (connection); + g_return_if_fail (s_wifi); + + ssid = nm_setting_wireless_get_ssid (s_wifi); + g_return_if_fail (ssid); + + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless) Stage 2 of 5 (Device Configure) " + "successful. %s '%s'.", + nm_device_get_iface (device), + priv->mode == NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot" : + "Connected to wireless network", + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)"); + nm_device_activate_schedule_stage3_ip_config_start (device); + } else if (devstate == NM_DEVICE_STATE_ACTIVATED) + periodic_update (self, NULL); + break; + case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED: + if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) { + /* Disconnect of an 802.1x/LEAP connection during authentication, + * or disconnect of a WPA-PSK connection during the 4-way handshake, + * often means secrets are wrong. Not always the case, but until we + * have more information from wpa_supplicant about why the + * disconnect happened this is the best we can do. + */ + if (handle_8021x_or_psk_auth_fail (self, new_state, old_state, disconnect_reason)) + break; + } + + /* Otherwise it might be a stupid driver or some transient error, so + * let the supplicant try to reconnect a few more times. Give it more + * time if a scan is in progress since the link might be dropped during + * the scan but will be re-established when the scan is done. + */ + if (devstate == NM_DEVICE_STATE_ACTIVATED) { + if (priv->link_timeout_id == 0) { + priv->link_timeout_id = g_timeout_add_seconds (scanning ? 30 : 15, link_timeout_cb, self); + priv->ssid_found = FALSE; + } + } + break; + case NM_SUPPLICANT_INTERFACE_STATE_DOWN: + cleanup_association_attempt (self, FALSE); + + if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY) + nm_device_remove_pending_action (device, "waiting for supplicant", TRUE); + + /* If the device is already in UNAVAILABLE state then the state change + * is a NOP and the interface won't be re-acquired in the device state + * change handler. So ensure we have a new one here so that we're + * ready if the supplicant comes back. + */ + supplicant_interface_release (self); + supplicant_interface_acquire (self); + + nm_device_state_changed (device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + break; + default: + break; + } + + /* Signal scanning state changes */ + if ( new_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING + || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING) + g_object_notify (G_OBJECT (self), "scanning"); +} + +static void +supplicant_iface_connection_error_cb (NMSupplicantInterface *iface, + const char *name, + const char *message, + NMDeviceWifi *self) +{ + NMDevice *device = NM_DEVICE (self); + + if (nm_device_is_activating (device)) { + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): supplicant association failed: %s - %s", + nm_device_get_iface (device), name, message); + + cleanup_association_attempt (self, TRUE); + nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } +} + +static void +remove_supplicant_interface_error_handler (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->sup_iface) { + g_signal_handlers_disconnect_by_func (priv->sup_iface, + supplicant_iface_connection_error_cb, + self); + } +} + +static void +supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface, + GParamSpec *pspec, + NMDeviceWifi *self) +{ + NMDeviceState state; + gboolean scanning; + + scanning = nm_supplicant_interface_get_scanning (iface); + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): now %s", + nm_device_get_iface (NM_DEVICE (self)), + scanning ? "scanning" : "idle"); + + g_object_notify (G_OBJECT (self), "scanning"); + + /* Run a quick update of current AP when coming out of a scan */ + state = nm_device_get_state (NM_DEVICE (self)); + if (!scanning && state == NM_DEVICE_STATE_ACTIVATED) + periodic_update (self, NULL); +} + +static NMActStageReturn +handle_auth_or_fail (NMDeviceWifi *self, + NMActRequest *req, + gboolean new_secrets) +{ + const char *setting_name; + guint32 tries; + NMConnection *connection; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + + g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), NM_ACT_STAGE_RETURN_FAILURE); + + if (!req) { + req = nm_device_get_act_request (NM_DEVICE (self)); + g_assert (req); + } + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + tries = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES)); + if (tries > 3) + return NM_ACT_STAGE_RETURN_FAILURE; + + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); + + nm_connection_clear_secrets (connection); + setting_name = nm_connection_need_secrets (connection, NULL); + if (setting_name) { + NMSettingsGetSecretsFlags flags = NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + if (new_secrets) + flags |= NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW; + nm_act_request_get_secrets (req, setting_name, flags, NULL, wifi_secrets_cb, self); + + g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, GUINT_TO_POINTER (++tries)); + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else + nm_log_warn (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets."); + + return ret; +} + +/* + * supplicant_connection_timeout_cb + * + * Called when the supplicant has been unable to connect to an access point + * within a specified period of time. + */ +static gboolean +supplicant_connection_timeout_cb (gpointer user_data) +{ + NMDevice *dev = NM_DEVICE (user_data); + NMDeviceWifi *self = NM_DEVICE_WIFI (user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMActRequest *req; + NMConnection *connection; + + cleanup_association_attempt (self, TRUE); + + if (!nm_device_is_activating (dev)) + return FALSE; + + /* Timed out waiting for a successful connection to the AP; if the AP's + * security requires network-side authentication (like WPA or 802.1x) + * and the connection attempt timed out then it's likely the authentication + * information (passwords, pin codes, etc) are wrong. + */ + + req = nm_device_get_act_request (dev); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + if ( priv->mode == NM_802_11_MODE_ADHOC + || priv->mode == NM_802_11_MODE_AP) { + /* In Ad-Hoc and AP modes there's nothing to check the encryption key + * (if any), so supplicant timeouts here are almost certainly the wifi + * driver being really stupid. + */ + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): %s network creation took " + "too long, failing activation.", + nm_device_get_iface (dev), + priv->mode == NM_802_11_MODE_ADHOC ? "Ad-Hoc" : "Hotspot"); + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + return FALSE; + } + + g_assert (priv->mode == NM_802_11_MODE_INFRA); + + if (priv->ssid_found && nm_connection_get_setting_wireless_security (connection)) { + guint64 timestamp = 0; + gboolean new_secrets = TRUE; + + /* Connection failed; either driver problems, the encryption key is + * wrong, or the passwords or certificates were wrong. + */ + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): association took too long.", + nm_device_get_iface (dev)); + + /* Ask for new secrets only if we've never activated this connection + * before. If we've connected before, don't bother the user with + * dialogs, just retry or fail, and if we never connect the user can + * fix the password somewhere else. + */ + if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), ×tamp)) + new_secrets = !timestamp; + + if (handle_auth_or_fail (self, req, new_secrets) == NM_ACT_STAGE_RETURN_POSTPONE) { + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): asking for new secrets", + nm_device_get_iface (dev)); + } else { + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } + } else { + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): association took too long, " + "failing activation.", + nm_device_get_iface (dev)); + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, + priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT : + NM_DEVICE_STATE_REASON_SSID_NOT_FOUND); + } + + return FALSE; +} + +static NMSupplicantConfig * +build_supplicant_config (NMDeviceWifi *self, + NMConnection *connection, + guint32 fixed_freq) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSupplicantConfig *config = NULL; + NMSettingWireless *s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + + g_return_val_if_fail (self != NULL, NULL); + + s_wireless = nm_connection_get_setting_wireless (connection); + g_return_val_if_fail (s_wireless != NULL, NULL); + + config = nm_supplicant_config_new (); + if (!config) + return NULL; + + /* Warn if AP mode may not be supported */ + if ( g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0 + && nm_supplicant_interface_get_ap_support (priv->sup_iface) == AP_SUPPORT_UNKNOWN) { + nm_log_warn (LOGD_WIFI, "Supplicant may not support AP mode; connection may time out."); + } + + if (!nm_supplicant_config_add_setting_wireless (config, + s_wireless, + fixed_freq)) { + nm_log_err (LOGD_WIFI, "Couldn't add 802-11-wireless setting to supplicant config."); + goto error; + } + + s_wireless_sec = nm_connection_get_setting_wireless_security (connection); + if (s_wireless_sec) { + NMSetting8021x *s_8021x; + const char *con_uuid = nm_connection_get_uuid (connection); + + g_assert (con_uuid); + s_8021x = nm_connection_get_setting_802_1x (connection); + if (!nm_supplicant_config_add_setting_wireless_security (config, + s_wireless_sec, + s_8021x, + con_uuid)) { + nm_log_err (LOGD_WIFI, "Couldn't add 802-11-wireless-security setting to " + "supplicant config."); + goto error; + } + } else { + if (!nm_supplicant_config_add_no_security (config)) { + nm_log_err (LOGD_WIFI, "Couldn't add unsecured option to supplicant config."); + goto error; + } + } + + return config; + +error: + g_object_unref (config); + return NULL; +} + +/****************************************************************************/ + +static void +update_permanent_hw_address (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + struct ifreq req; + struct ethtool_perm_addr *epaddr = NULL; + int fd, ret; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_HW, "could not open control socket."); + return; + } + + /* Get permanent MAC address */ + memset (&req, 0, sizeof (struct ifreq)); + strncpy (req.ifr_name, nm_device_get_iface (dev), IFNAMSIZ); + + epaddr = g_malloc0 (sizeof (struct ethtool_perm_addr) + ETH_ALEN); + epaddr->cmd = ETHTOOL_GPERMADDR; + epaddr->size = ETH_ALEN; + req.ifr_data = (void *) epaddr; + + errno = 0; + ret = ioctl (fd, SIOCETHTOOL, &req); + if ((ret < 0) || !nm_ethernet_address_is_valid ((struct ether_addr *) epaddr->data)) { + nm_log_dbg (LOGD_HW | LOGD_ETHER, "(%s): unable to read permanent MAC address (error %d)", + nm_device_get_iface (dev), errno); + /* Fall back to current address */ + memcpy (epaddr->data, nm_device_get_hw_address (dev, NULL), ETH_ALEN); + } + + if (memcmp (&priv->perm_hw_addr, epaddr->data, ETH_ALEN)) { + memcpy (&priv->perm_hw_addr, epaddr->data, ETH_ALEN); + g_object_notify (G_OBJECT (dev), NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS); + } + + g_free (epaddr); + close (fd); +} + +static void +update_initial_hw_address (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + char *mac_str; + + /* This sets initial MAC address from current MAC address. It should only + * be called from NMDevice constructor() to really get the initial address. + */ + memcpy (priv->initial_hw_addr, nm_device_get_hw_address (dev, NULL), ETH_ALEN); + + mac_str = nm_utils_hwaddr_ntoa (priv->initial_hw_addr, ARPHRD_ETHER); + nm_log_dbg (LOGD_DEVICE | LOGD_ETHER, "(%s): read initial MAC address %s", + nm_device_get_iface (dev), mac_str); + g_free (mac_str); +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMActStageReturn ret; + NMAccessPoint *ap = NULL; + NMActRequest *req; + NMConnection *connection; + NMSettingWireless *s_wireless; + const GByteArray *cloned_mac; + GSList *iter; + const char *mode; + const char *ap_path; + + ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage1_prepare (dev, reason); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) + return ret; + + req = nm_device_get_act_request (NM_DEVICE (self)); + g_return_val_if_fail (req != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_connection (req); + g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + s_wireless = nm_connection_get_setting_wireless (connection); + g_assert (s_wireless); + + mode = nm_setting_wireless_get_mode (s_wireless); + if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0) + priv->mode = NM_802_11_MODE_INFRA; + else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) + priv->mode = NM_802_11_MODE_ADHOC; + else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0) { + priv->mode = NM_802_11_MODE_AP; + + /* Scanning not done in AP mode; clear the scan list */ + remove_all_aps (self); + } + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_MODE); + + /* The kernel doesn't support Ad-Hoc WPA connections well at this time, + * and turns them into open networks. It's been this way since at least + * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks. + */ + if (is_adhoc_wpa (connection)) { + nm_log_warn (LOGD_WIFI, "Ad-Hoc WPA disabled due to kernel bugs"); + *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* Set spoof MAC to the interface */ + cloned_mac = nm_setting_wireless_get_cloned_mac_address (s_wireless); + if (cloned_mac && (cloned_mac->len == ETH_ALEN)) + nm_device_set_hw_addr (dev, (const guint8 *) cloned_mac->data, "set", LOGD_WIFI); + + /* AP mode never uses a specific object or existing scanned AP */ + if (priv->mode != NM_802_11_MODE_AP) { + + ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req)); + ap = ap_path ? get_ap_by_path (self, ap_path) : NULL; + if (ap) + goto done; + + /* Find a compatible AP in the scan list */ + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) { + NMAccessPoint *candidate = NM_AP (iter->data); + + if (nm_ap_check_compatible (candidate, connection)) { + ap = candidate; + break; + } + } + } + + if (ap) { + nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_ap_get_dbus_path (ap)); + goto done; + } + + /* If the user is trying to connect to an AP that NM doesn't yet know about + * (hidden network or something) or starting a Hotspot, create an fake AP + * from the security settings in the connection. This "fake" AP gets used + * until the real one is found in the scan list (Ad-Hoc or Hidden), or until + * the device is deactivated (Hotspot). + */ + ap = nm_ap_new_fake_from_connection (connection); + g_return_val_if_fail (ap != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + if (nm_ap_get_mode (ap) == NM_802_11_MODE_INFRA) + nm_ap_set_broadcast (ap, FALSE); + else if (nm_ap_is_hotspot (ap)) + nm_ap_set_address (ap, (const struct ether_addr *) nm_device_get_hw_address (dev, NULL)); + + priv->ap_list = g_slist_prepend (priv->ap_list, ap); + nm_ap_export_to_dbus (ap); + g_object_freeze_notify (G_OBJECT (self)); + set_current_ap (self, ap, FALSE, FALSE); + emit_ap_added_removed (self, ACCESS_POINT_ADDED, ap, TRUE); + g_object_thaw_notify (G_OBJECT (self)); + nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_ap_get_dbus_path (ap)); + return NM_ACT_STAGE_RETURN_SUCCESS; + +done: + set_current_ap (self, ap, TRUE, FALSE); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +ensure_hotspot_frequency (NMDeviceWifi *self, + NMSettingWireless *s_wifi, + NMAccessPoint *ap) +{ + const char *band = nm_setting_wireless_get_band (s_wifi); + const guint32 a_freqs[] = { 5180, 5200, 5220, 5745, 5765, 5785, 5805, 0 }; + const guint32 bg_freqs[] = { 2412, 2437, 2462, 2472, 0 }; + guint32 freq = 0; + + g_assert (ap); + + if (nm_ap_get_freq (ap)) + return; + + if (g_strcmp0 (band, "a") == 0) + freq = nm_platform_wifi_find_frequency (nm_device_get_ifindex (NM_DEVICE (self)), a_freqs); + else + freq = nm_platform_wifi_find_frequency (nm_device_get_ifindex (NM_DEVICE (self)), bg_freqs); + + if (!freq) + freq = (g_strcmp0 (band, "a") == 0) ? 5180 : 2462; + + nm_ap_set_freq (ap, freq); +} + +static NMActStageReturn +act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + const char *iface = nm_device_get_iface (dev); + NMSupplicantConfig *config = NULL; + NMActRequest *req; + NMAccessPoint *ap; + NMConnection *connection; + const char *setting_name; + NMSettingWireless *s_wireless; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + remove_supplicant_timeouts (self); + + req = nm_device_get_act_request (dev); + g_assert (req); + + ap = priv->current_ap; + if (!ap) { + *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED; + goto out; + } + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + s_wireless = nm_connection_get_setting_wireless (connection); + g_assert (s_wireless); + + /* If we need secrets, get them */ + setting_name = nm_connection_need_secrets (connection, NULL); + if (setting_name) { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): access point '%s' has security," + " but secrets are required.", + iface, nm_connection_get_id (connection)); + + ret = handle_auth_or_fail (self, req, FALSE); + if (ret == NM_ACT_STAGE_RETURN_FAILURE) + *reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + goto out; + } + + /* have secrets, or no secrets required */ + if (nm_connection_get_setting_wireless_security (connection)) { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): connection '%s' has security" + ", and secrets exist. No new secrets needed.", + iface, nm_connection_get_id (connection)); + } else { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): connection '%s' requires no " + "security. No secrets needed.", + iface, nm_connection_get_id (connection)); + } + + priv->ssid_found = FALSE; + + /* Supplicant requires an initial frequency for Ad-Hoc and Hotspot; if the user + * didn't specify one and we didn't find an AP that matched the connection, + * just pick a frequency the device supports. + */ + if ((nm_ap_get_mode (ap) == NM_802_11_MODE_ADHOC) || nm_ap_is_hotspot (ap)) + ensure_hotspot_frequency (self, s_wireless, ap); + + /* Build up the supplicant configuration */ + config = build_supplicant_config (self, connection, nm_ap_get_freq (ap)); + if (config == NULL) { + nm_log_err (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): couldn't build wireless configuration.", + iface); + *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED; + goto out; + } + + /* Hook up error signal handler to capture association errors */ + g_signal_connect (priv->sup_iface, + NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR, + G_CALLBACK (supplicant_iface_connection_error_cb), + self); + + if (!nm_supplicant_interface_set_config (priv->sup_iface, config)) { + nm_log_err (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): couldn't send wireless " + "configuration to the supplicant.", iface); + *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED; + goto out; + } + + /* Set up a timeout on the association attempt to fail after 25 seconds */ + priv->sup_timeout_id = g_timeout_add_seconds (25, supplicant_connection_timeout_cb, self); + + if (!priv->periodic_source_id) + priv->periodic_source_id = g_timeout_add_seconds (6, periodic_update_cb, self); + + /* We'll get stage3 started when the supplicant connects */ + ret = NM_ACT_STAGE_RETURN_POSTPONE; + +out: + if (ret == NM_ACT_STAGE_RETURN_FAILURE) + cleanup_association_attempt (self, TRUE); + + if (config) { + /* Supplicant interface object refs the config; we no longer care about + * it after this function. + */ + g_object_unref (config); + } + return ret; +} + +static NMActStageReturn +act_stage3_ip4_config_start (NMDevice *device, + NMIP4Config **out_config, + NMDeviceStateReason *reason) +{ + NMConnection *connection; + NMSettingIP4Config *s_ip4; + const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + + connection = nm_device_get_connection (device); + g_assert (connection); + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (s_ip4) + method = nm_setting_ip4_config_get_method (s_ip4); + + /* Indicate that a critical protocol is about to start */ + if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) + nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (device), TRUE); + + return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip4_config_start (device, out_config, reason); +} + +static NMActStageReturn +act_stage3_ip6_config_start (NMDevice *device, + NMIP6Config **out_config, + NMDeviceStateReason *reason) +{ + NMConnection *connection; + NMSettingIP6Config *s_ip6; + const char *method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + + connection = nm_device_get_connection (device); + g_assert (connection); + s_ip6 = nm_connection_get_setting_ip6_config (connection); + if (s_ip6) + method = nm_setting_ip6_config_get_method (s_ip6); + + /* Indicate that a critical protocol is about to start */ + if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 || + strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) + nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (device), TRUE); + + return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip6_config_start (device, out_config, reason); +} + +static void +ip4_config_pre_commit (NMDevice *device, NMIP4Config *config) +{ + NMConnection *connection; + NMSettingWireless *s_wifi; + guint32 mtu; + + connection = nm_device_get_connection (device); + g_assert (connection); + s_wifi = nm_connection_get_setting_wireless (connection); + g_assert (s_wifi); + + /* MTU override */ + mtu = nm_setting_wireless_get_mtu (s_wifi); + if (mtu) + nm_ip4_config_set_mtu (config, mtu); +} + +static gboolean +is_static_wep (NMConnection *connection) +{ + NMSettingWirelessSecurity *s_wsec; + const char *str; + + g_return_val_if_fail (connection != NULL, FALSE); + + s_wsec = nm_connection_get_setting_wireless_security (connection); + if (!s_wsec) + return FALSE; + + str = nm_setting_wireless_security_get_key_mgmt (s_wsec); + if (g_strcmp0 (str, "none") != 0) + return FALSE; + + str = nm_setting_wireless_security_get_auth_alg (s_wsec); + if (g_strcmp0 (str, "leap") == 0) + return FALSE; + + return TRUE; +} + +static NMActStageReturn +handle_ip_config_timeout (NMDeviceWifi *self, + NMConnection *connection, + gboolean may_fail, + gboolean *chain_up, + NMDeviceStateReason *reason) +{ + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + + g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP) { + *chain_up = TRUE; + return ret; + } + + /* If IP configuration times out and it's a static WEP connection, that + * usually means the WEP key is wrong. WEP's Open System auth mode has + * no provision for figuring out if the WEP key is wrong, so you just have + * to wait for DHCP to fail to figure it out. For all other WiFi security + * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the + * connection would have failed before IP configuration. + */ + if (!may_fail && is_static_wep (connection)) { + /* Activation failed, we must have bad encryption key */ + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): could not get IP configuration for " + "connection '%s'.", + nm_device_get_iface (NM_DEVICE (self)), + nm_connection_get_id (connection)); + + ret = handle_auth_or_fail (self, NULL, TRUE); + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): asking for new secrets", + nm_device_get_iface (NM_DEVICE (self))); + } else { + *reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + } + } else { + /* Not static WEP or failure allowed; let superclass handle it */ + *chain_up = TRUE; + } + + return ret; +} + + +static NMActStageReturn +act_stage4_ip4_config_timeout (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMConnection *connection; + NMSettingIP4Config *s_ip4; + gboolean may_fail = FALSE, chain_up = FALSE; + NMActStageReturn ret; + + connection = nm_device_get_connection (dev); + g_assert (connection); + + s_ip4 = nm_connection_get_setting_ip4_config (connection); + may_fail = nm_setting_ip4_config_get_may_fail (s_ip4); + + ret = handle_ip_config_timeout (NM_DEVICE_WIFI (dev), connection, may_fail, &chain_up, reason); + if (chain_up) + ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip4_config_timeout (dev, reason); + + return ret; +} + +static NMActStageReturn +act_stage4_ip6_config_timeout (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMConnection *connection; + NMSettingIP6Config *s_ip6; + gboolean may_fail = FALSE, chain_up = FALSE; + NMActStageReturn ret; + + connection = nm_device_get_connection (dev); + g_assert (connection); + + s_ip6 = nm_connection_get_setting_ip6_config (connection); + may_fail = nm_setting_ip6_config_get_may_fail (s_ip6); + + ret = handle_ip_config_timeout (NM_DEVICE_WIFI (dev), connection, may_fail, &chain_up, reason); + if (chain_up) + ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip6_config_timeout (dev, reason); + + return ret; +} + +static void +activation_success_handler (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + int ifindex = nm_device_get_ifindex (dev); + NMAccessPoint *ap; + struct ether_addr bssid = { {0x0, 0x0, 0x0, 0x0, 0x0, 0x0} }; + NMAccessPoint *tmp_ap = NULL; + NMActRequest *req; + NMConnection *connection; + + req = nm_device_get_act_request (dev); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + /* Clear any critical protocol notification in the wifi stack */ + nm_platform_wifi_indicate_addressing_running (ifindex, FALSE); + + /* Clear wireless secrets tries on success */ + g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL); + + ap = priv->current_ap; + + /* If the AP isn't fake, it was found in the scan list and all its + * details are known. + */ + if (!ap || !nm_ap_get_fake (ap)){ + ap = NULL; + goto done; + } + + /* If the activate AP was fake, it probably won't have a BSSID at all. + * But if activation was successful, the card will know the BSSID. Grab + * the BSSID off the card and fill in the BSSID of the activation AP. + */ + nm_platform_wifi_get_bssid (ifindex, &bssid); + if (!nm_ethernet_address_is_valid (nm_ap_get_address (ap))) + nm_ap_set_address (ap, &bssid); + if (!nm_ap_get_freq (ap)) + nm_ap_set_freq (ap, nm_platform_wifi_get_frequency (ifindex)); + if (!nm_ap_get_max_bitrate (ap)) + nm_ap_set_max_bitrate (ap, nm_platform_wifi_get_rate (ifindex)); + + tmp_ap = find_active_ap (self, ap, TRUE); + if (tmp_ap) { + const GByteArray *ssid = nm_ap_get_ssid (tmp_ap); + + /* Found a better match in the scan list than the fake AP. Use it + * instead. + */ + + /* If the better match was a hidden AP, update it's SSID */ + if (!ssid || nm_utils_is_empty_ssid (ssid->data, ssid->len)) + nm_ap_set_ssid (tmp_ap, nm_ap_get_ssid (ap)); + + nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), + nm_ap_get_dbus_path (tmp_ap)); + } + +done: + periodic_update (self, ap); + + /* ap might be already unrefed, because it was a fake_ap. But we don't touch it... */ + if (tmp_ap && ap == priv->current_ap) { + /* Strange, we would expect periodic_update() to find a better AP + * then the fake one and reset it. Reset the fake current_ap to NULL + * now, which will remove the fake ap. + **/ + set_current_ap (self, NULL, TRUE, FALSE); + } + + /* No need to update seen BSSIDs cache, that is done by set_current_ap() already */ + + /* Reset scan interval to something reasonable */ + priv->scan_interval = SCAN_INTERVAL_MIN + (SCAN_INTERVAL_STEP * 2); +} + +static void +activation_failure_handler (NMDevice *dev) +{ + NMConnection *connection; + + connection = nm_device_get_connection (dev); + g_assert (connection); + + /* Clear wireless secrets tries on failure */ + g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL); + + /* Clear any critical protocol notification in the wifi stack */ + nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (dev), FALSE); +} + +static void +device_state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + gboolean clear_aps = FALSE; + + if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) { + /* Clean up the supplicant interface because in these states the + * device cannot be used. + */ + if (priv->sup_iface) + supplicant_interface_release (self); + + if (priv->periodic_source_id) { + g_source_remove (priv->periodic_source_id); + priv->periodic_source_id = 0; + } + + cleanup_association_attempt (self, TRUE); + remove_all_aps (self); + } + + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + clear_aps = TRUE; + break; + case NM_DEVICE_STATE_UNAVAILABLE: + /* If the device is enabled and the supplicant manager is ready, + * acquire a supplicant interface and transition to DISCONNECTED because + * the device is now ready to use. + */ + if (priv->enabled && (nm_device_get_firmware_missing (device) == FALSE)) { + if (!priv->sup_iface) + supplicant_interface_acquire (self); + } + clear_aps = TRUE; + break; + case NM_DEVICE_STATE_NEED_AUTH: + if (priv->sup_iface) + nm_supplicant_interface_disconnect (priv->sup_iface); + break; + case NM_DEVICE_STATE_IP_CHECK: + /* Clear any critical protocol notification in the wifi stack */ + nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (device), FALSE); + break; + case NM_DEVICE_STATE_ACTIVATED: + activation_success_handler (device); + break; + case NM_DEVICE_STATE_FAILED: + activation_failure_handler (device); + break; + case NM_DEVICE_STATE_DISCONNECTED: + /* Kick off a scan to get latest results */ + priv->scan_interval = SCAN_INTERVAL_MIN; + cancel_pending_scan (self); + request_wireless_scan (self); + break; + default: + break; + } + + if (clear_aps) + remove_all_aps (self); +} + +static void +set_enabled (NMDevice *device, gboolean enabled) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMDeviceState state; + + if (priv->enabled == enabled) + return; + + priv->enabled = enabled; + + nm_log_dbg (LOGD_WIFI, "(%s): device now %s", + nm_device_get_iface (NM_DEVICE (device)), + enabled ? "enabled" : "disabled"); + + state = nm_device_get_state (NM_DEVICE (self)); + if (state < NM_DEVICE_STATE_UNAVAILABLE) { + nm_log_dbg (LOGD_WIFI, "(%s): %s blocked by UNMANAGED state", + enabled ? "enable" : "disable", + nm_device_get_iface (NM_DEVICE (device))); + return; + } + + if (enabled) { + gboolean no_firmware = FALSE; + + if (state != NM_DEVICE_STATE_UNAVAILABLE) + nm_log_warn (LOGD_CORE, "not in expected unavailable state!"); + + if (!nm_device_bring_up (NM_DEVICE (self), TRUE, &no_firmware)) { + nm_log_dbg (LOGD_WIFI, "(%s): enable blocked by failure to bring device up", + nm_device_get_iface (NM_DEVICE (device))); + + if (no_firmware) + nm_device_set_firmware_missing (NM_DEVICE (device), TRUE); + else { + /* The device sucks, or the kernel was lying to us about the killswitch state */ + priv->enabled = FALSE; + } + return; + } + + /* Re-initialize the supplicant interface and wait for it to be ready */ + if (priv->sup_iface) + supplicant_interface_release (self); + supplicant_interface_acquire (self); + + nm_log_dbg (LOGD_WIFI, "(%s): enable waiting on supplicant state", + nm_device_get_iface (NM_DEVICE (device))); + } else { + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + nm_device_take_down (NM_DEVICE (self), TRUE); + } +} + +/********************************************************************/ + +NMDevice * +nm_device_wifi_new (NMPlatformLink *platform_device) +{ + g_return_val_if_fail (platform_device != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_WIFI, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "802.11 WiFi", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI, + NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN, + NULL); +} + +static void +nm_device_wifi_init (NMDeviceWifi *self) +{ + NM_DEVICE_WIFI_GET_PRIVATE (self)->mode = NM_802_11_MODE_INFRA; +} + +static void +dispose (GObject *object) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->disposed) { + G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object); + return; + } + + priv->disposed = TRUE; + + if (priv->periodic_source_id) { + g_source_remove (priv->periodic_source_id); + priv->periodic_source_id = 0; + } + + cleanup_association_attempt (self, TRUE); + supplicant_interface_release (self); + + g_clear_object (&priv->sup_mgr); + + remove_all_aps (self); + + G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceWifi *device = NM_DEVICE_WIFI (object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device); + GPtrArray *array; + GSList *iter; + + switch (prop_id) { + case PROP_PERM_HW_ADDRESS: + g_value_take_string (value, nm_utils_hwaddr_ntoa (&priv->perm_hw_addr, ARPHRD_ETHER)); + break; + case PROP_MODE: + g_value_set_uint (value, priv->mode); + break; + case PROP_BITRATE: + g_value_set_uint (value, priv->rate); + break; + case PROP_CAPABILITIES: + g_value_set_uint (value, priv->capabilities); + break; + case PROP_ACCESS_POINTS: + array = g_ptr_array_sized_new (4); + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) + g_ptr_array_add (array, g_strdup (nm_ap_get_dbus_path (NM_AP (iter->data)))); + g_value_take_boxed (value, array); + break; + case PROP_ACTIVE_ACCESS_POINT: + if (priv->current_ap) + g_value_set_boxed (value, nm_ap_get_dbus_path (priv->current_ap)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_SCANNING: + g_value_set_boolean (value, nm_supplicant_interface_get_scanning (priv->sup_iface)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +nm_device_wifi_class_init (NMDeviceWifiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceWifiPrivate)); + + object_class->constructor = constructor; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + parent_class->bring_up = bring_up; + parent_class->update_permanent_hw_address = update_permanent_hw_address; + parent_class->update_initial_hw_address = update_initial_hw_address; + parent_class->can_auto_connect = can_auto_connect; + parent_class->is_available = is_available; + parent_class->check_connection_compatible = check_connection_compatible; + parent_class->check_connection_available = check_connection_available; + parent_class->check_connection_available_wifi_hidden = check_connection_available_wifi_hidden; + parent_class->complete_connection = complete_connection; + parent_class->set_enabled = set_enabled; + + parent_class->act_stage1_prepare = act_stage1_prepare; + parent_class->act_stage2_config = act_stage2_config; + parent_class->ip4_config_pre_commit = ip4_config_pre_commit; + parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start; + parent_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start; + parent_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout; + parent_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout; + parent_class->deactivate = deactivate; + + parent_class->state_changed = device_state_changed; + + klass->scanning_allowed = scanning_allowed; + + /* Properties */ + g_object_class_install_property (object_class, PROP_PERM_HW_ADDRESS, + g_param_spec_string (NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS, + "Permanent MAC Address", + "Permanent hardware MAC address", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_MODE, + g_param_spec_uint (NM_DEVICE_WIFI_MODE, + "Mode", + "Mode", + NM_802_11_MODE_UNKNOWN, + NM_802_11_MODE_AP, + NM_802_11_MODE_INFRA, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_BITRATE, + g_param_spec_uint (NM_DEVICE_WIFI_BITRATE, + "Bitrate", + "Bitrate", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_ACCESS_POINTS, + g_param_spec_boxed (NM_DEVICE_WIFI_ACCESS_POINTS, + "Access points", + "Access points", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_ACTIVE_ACCESS_POINT, + g_param_spec_boxed (NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT, + "Active access point", + "Currently active access point", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_CAPABILITIES, + g_param_spec_uint (NM_DEVICE_WIFI_CAPABILITIES, + "Wireless Capabilities", + "Wireless Capabilities", + 0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_SCANNING, + g_param_spec_boolean (NM_DEVICE_WIFI_SCANNING, + "Scanning", + "Scanning", + FALSE, + G_PARAM_READABLE)); + + /* Signals */ + signals[ACCESS_POINT_ADDED] = + g_signal_new ("access-point-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWifiClass, access_point_added), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + signals[ACCESS_POINT_REMOVED] = + g_signal_new ("access-point-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + signals[SCANNING_ALLOWED] = + g_signal_new ("scanning-allowed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_allowed), + scanning_allowed_accumulator, NULL, NULL, + G_TYPE_BOOLEAN, 0); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_wifi_object_info); + + dbus_g_error_domain_register (NM_WIFI_ERROR, NULL, NM_TYPE_WIFI_ERROR); +} + + diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h new file mode 100644 index 000000000..f0a1beacd --- /dev/null +++ b/src/devices/wifi/nm-device-wifi.h @@ -0,0 +1,94 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2005 - 2010 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef NM_DEVICE_WIFI_H +#define NM_DEVICE_WIFI_H + +#include <glib-object.h> +#include <dbus/dbus.h> +#include <net/ethernet.h> + +#include "nm-device.h" +#include "nm-wifi-ap.h" + +struct NMAccessPointList; + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_WIFI (nm_device_wifi_get_type ()) +#define NM_DEVICE_WIFI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifi)) +#define NM_DEVICE_WIFI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass)) +#define NM_IS_DEVICE_WIFI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIFI)) +#define NM_IS_DEVICE_WIFI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_WIFI)) +#define NM_DEVICE_WIFI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass)) + +typedef enum { + NM_WIFI_ERROR_CONNECTION_NOT_WIRELESS = 0, /*< nick=ConnectionNotWireless >*/ + NM_WIFI_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ + NM_WIFI_ERROR_ACCESS_POINT_NOT_FOUND, /*< nick=AccessPointNotFound >*/ + NM_WIFI_ERROR_SCAN_NOT_ALLOWED, /*< nick=ScanNotAllowed >*/ + NM_WIFI_ERROR_AP_MODE_UNSUPPORTED, /*< nick=ApModeUnsupported >*/ + NM_WIFI_ERROR_ADHOC_MODE_UNSUPPORTED, /*< nick=AdhocModeUnsupported >*/ +} NMWifiError; + +#define NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS "perm-hw-address" +#define NM_DEVICE_WIFI_MODE "mode" +#define NM_DEVICE_WIFI_BITRATE "bitrate" +#define NM_DEVICE_WIFI_ACCESS_POINTS "access-points" +#define NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT "active-access-point" +#define NM_DEVICE_WIFI_CAPABILITIES "wireless-capabilities" +#define NM_DEVICE_WIFI_SCANNING "scanning" + +#ifndef NM_DEVICE_WIFI_DEFINED +#define NM_DEVICE_WIFI_DEFINED +typedef struct _NMDeviceWifi NMDeviceWifi; +#endif + +typedef struct _NMDeviceWifiClass NMDeviceWifiClass; +typedef struct _NMDeviceWifiPrivate NMDeviceWifiPrivate; + +struct _NMDeviceWifi +{ + NMDevice parent; + + /*< private >*/ + NMDeviceWifiPrivate *priv; +}; + +struct _NMDeviceWifiClass +{ + NMDeviceClass parent; + + /* Signals */ + void (*access_point_added) (NMDeviceWifi *device, NMAccessPoint *ap); + void (*access_point_removed) (NMDeviceWifi *device, NMAccessPoint *ap); + gboolean (*scanning_allowed) (NMDeviceWifi *device); +}; + + +GType nm_device_wifi_get_type (void); + +NMDevice *nm_device_wifi_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_WIFI_H */ diff --git a/src/devices/wifi/nm-wifi-ap-utils.c b/src/devices/wifi/nm-wifi-ap-utils.c new file mode 100644 index 000000000..9b03cbd45 --- /dev/null +++ b/src/devices/wifi/nm-wifi-ap-utils.c @@ -0,0 +1,721 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2011 Red Hat, Inc. + */ + +#include <config.h> +#include <string.h> +#include <stdlib.h> + +#include "nm-wifi-ap-utils.h" + +static gboolean +verify_no_wep (NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error) +{ + if ( nm_setting_wireless_security_get_wep_key (s_wsec, 0) + || nm_setting_wireless_security_get_wep_key (s_wsec, 1) + || nm_setting_wireless_security_get_wep_key (s_wsec, 2) + || nm_setting_wireless_security_get_wep_key (s_wsec, 3) + || nm_setting_wireless_security_get_wep_tx_keyidx (s_wsec) + || nm_setting_wireless_security_get_wep_key_type (s_wsec)) { + /* Dynamic WEP cannot have any WEP keys set */ + g_set_error (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "%s is incompatible with static WEP keys", tag); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_leap (NMSettingWirelessSecurity *s_wsec, + NMSetting8021x *s_8021x, + gboolean adhoc, + GError **error) +{ + const char *key_mgmt, *auth_alg, *leap_username; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username (s_wsec); + + /* One (or both) of two things indicates we want LEAP: + * 1) auth_alg == 'leap' + * 2) valid leap_username + * + * LEAP always requires a LEAP username. + */ + + if (auth_alg) { + if (!strcmp (auth_alg, "leap")) { + /* LEAP authentication requires at least a LEAP username */ + if (!leap_username) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_LEAP_REQUIRES_USERNAME, + "LEAP requires a LEAP username"); + return FALSE; + } + } else if (leap_username) { + /* Leap username requires 'leap' auth */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "LEAP requires 'leap' authentication"); + return FALSE; + } + } + + if (leap_username) { + if (key_mgmt && strcmp (key_mgmt, "ieee8021x")) { + /* LEAP requires ieee8021x key management */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_LEAP_REQUIRES_802_1X, + "LEAP requires IEEE 802.1x key management"); + return FALSE; + } + } + + /* At this point if auth_alg is set it must be 'leap', and if key_mgmt + * is set it must be 'ieee8021x'. + */ + if (leap_username) { + if (auth_alg) + g_assert (strcmp (auth_alg, "leap") == 0); + if (key_mgmt) + g_assert (strcmp (key_mgmt, "ieee8021x") == 0); + + if (adhoc) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "LEAP incompatible with Ad-Hoc mode"); + return FALSE; + } + + if (!verify_no_wep (s_wsec, "LEAP", error)) + return FALSE; + + if (s_8021x) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_LEAP_REQUIRES_USERNAME, + "LEAP incompatible with 802.1x setting"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +verify_no_wpa (NMSettingWirelessSecurity *s_wsec, + const char *tag, + GError **error) +{ + const char *key_mgmt; + int n, i; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + if (key_mgmt && !strncmp (key_mgmt, "wpa", 3)) { + g_set_error (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "%s incompatible with any WPA key management", tag); + return FALSE; + } + + if (nm_setting_wireless_security_get_num_protos (s_wsec)) { + g_set_error (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "%s incompatible with any 'proto' setting", tag); + return FALSE; + } + + n = nm_setting_wireless_security_get_num_pairwise (s_wsec); + for (i = 0; i < n; i++) { + const char *pw; + + pw = nm_setting_wireless_security_get_pairwise (s_wsec, i); + if (!strcmp (pw, "tkip") || !strcmp (pw, "ccmp")) { + g_set_error (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "%s is incompatible with WPA pairwise ciphers", tag); + return FALSE; + } + } + + n = nm_setting_wireless_security_get_num_groups (s_wsec); + for (i = 0; i < n; i++) { + const char *gr; + + gr = nm_setting_wireless_security_get_group (s_wsec, i); + if (strcmp (gr, "wep40") && strcmp (gr, "wep104")) { + g_set_error (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "%s is incompatible with WPA group ciphers", tag); + return FALSE; + } + } + + if (nm_setting_wireless_security_get_psk (s_wsec)) { + g_set_error (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "%s is incompatible with a WPA Pre-Shared Key", tag); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_dynamic_wep (NMSettingWirelessSecurity *s_wsec, + NMSetting8021x *s_8021x, + gboolean adhoc, + GError **error) +{ + const char *key_mgmt, *auth_alg, *leap_username; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username (s_wsec); + + g_return_val_if_fail (leap_username == NULL, TRUE); + + if (key_mgmt) { + if (!strcmp (key_mgmt, "ieee8021x")) { + if (!s_8021x) { + /* 802.1x key management requires an 802.1x setting */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Dynamic WEP requires an 802.1x setting"); + return FALSE; + } + + if (auth_alg && strcmp (auth_alg, "open")) { + /* 802.1x key management must use "open" authentication */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Dynamic WEP requires 'open' authentication"); + return FALSE; + } + + /* Dynamic WEP incompatible with anything static WEP related */ + if (!verify_no_wep (s_wsec, "Dynamic WEP", error)) + return FALSE; + } else if (!strcmp (key_mgmt, "none")) { + if (s_8021x) { + /* 802.1x setting requires 802.1x key management */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Dynamic WEP requires 'ieee8021x' key management"); + return FALSE; + } + } + } else if (s_8021x) { + /* 802.1x setting incompatible with anything but 'open' auth */ + if (auth_alg && strcmp (auth_alg, "open")) { + /* 802.1x key management must use "open" authentication */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Dynamic WEP requires 'open' authentication"); + return FALSE; + } + + /* Dynamic WEP incompatible with anything static WEP related */ + if (!verify_no_wep (s_wsec, "Dynamic WEP", error)) + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_wpa_psk (NMSettingWirelessSecurity *s_wsec, + NMSetting8021x *s_8021x, + gboolean adhoc, + guint32 wpa_flags, + guint32 rsn_flags, + GError **error) +{ + const char *key_mgmt, *auth_alg, *tmp; + int n; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + + if (key_mgmt) { + if (!strcmp (key_mgmt, "wpa-psk") || !strcmp (key_mgmt, "wpa-none")) { + if (s_8021x) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA-PSK incompatible with 802.1x"); + return FALSE; + } + + if (auth_alg && strcmp (auth_alg, "open")) { + /* WPA must use "open" authentication */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA-PSK requires 'open' authentication"); + return FALSE; + } + } + + if (!strcmp (key_mgmt, "wpa-none")) { + if (!adhoc) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA Ad-Hoc requires an Ad-Hoc mode AP"); + return FALSE; + } + + /* Ad-Hoc WPA requires 'wpa' proto, 'none' pairwise, and 'tkip' group */ + n = nm_setting_wireless_security_get_num_protos (s_wsec); + tmp = (n > 0) ? nm_setting_wireless_security_get_proto (s_wsec, 0) : NULL; + if (n > 1 || !tmp || strcmp (tmp, "wpa")) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA Ad-Hoc requires 'wpa' proto"); + return FALSE; + } + + n = nm_setting_wireless_security_get_num_pairwise (s_wsec); + tmp = (n > 0) ? nm_setting_wireless_security_get_pairwise (s_wsec, 0) : NULL; + if (n > 1 || g_strcmp0 (tmp, "none")) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA Ad-Hoc requires 'none' pairwise cipher"); + return FALSE; + } + + n = nm_setting_wireless_security_get_num_groups (s_wsec); + tmp = (n > 0) ? nm_setting_wireless_security_get_group (s_wsec, 0) : NULL; + if (n > 1 || !tmp || strcmp (tmp, "tkip")) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA Ad-Hoc requires 'tkip' group cipher"); + return FALSE; + } + } + + if (!strcmp (key_mgmt, "wpa-psk")) { + /* Make sure the AP's capabilities support WPA-PSK */ + if ( !(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) + && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "AP does not support PSK but setting requires it"); + return FALSE; + } + } + } + + return TRUE; +} + +static gboolean +verify_wpa_eap (NMSettingWirelessSecurity *s_wsec, + NMSetting8021x *s_8021x, + guint32 wpa_flags, + guint32 rsn_flags, + GError **error) +{ + const char *key_mgmt, *auth_alg; + gboolean is_wpa_eap = FALSE; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + + if (key_mgmt) { + if (!strcmp (key_mgmt, "wpa-eap")) { + if (!s_8021x) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA-EAP requires an 802.1x setting"); + return FALSE; + } + + if (auth_alg && strcmp (auth_alg, "open")) { + /* WPA must use "open" authentication */ + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA-EAP requires 'open' authentication"); + return FALSE; + } + + is_wpa_eap = TRUE; + } else if (s_8021x) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Setting requires 802.1x but does not use 'wpa-eap' key management"); + return FALSE; + } + } + + if (is_wpa_eap || s_8021x) { + /* Make sure the AP's capabilities support WPA-EAP */ + if ( !(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "AP does not support 802.1x but setting requires it"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +verify_adhoc (NMSettingWirelessSecurity *s_wsec, + NMSetting8021x *s_8021x, + gboolean adhoc, + GError **error) +{ + const char *key_mgmt = NULL, *leap_username = NULL, *auth_alg = NULL; + + if (s_wsec) { + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username (s_wsec); + } + + if (adhoc) { + if (key_mgmt && strcmp (key_mgmt, "wpa-none") && strcmp (key_mgmt, "none")) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "AP mode is Ad-Hoc but setting requires Infrastructure security"); + return FALSE; + } + + if (s_8021x) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Ad-Hoc mode incompatible with 802.1x security"); + return FALSE; + } + + if (leap_username) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Ad-Hoc mode incompatible with LEAP security"); + return FALSE; + } + + if (auth_alg && strcmp (auth_alg, "open")) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Ad-Hoc mode requires 'open' authentication"); + return FALSE; + } + } else { + if (key_mgmt && !strcmp (key_mgmt, "wpa-none")) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "AP mode is Infrastructure but setting requires Ad-Hoc security"); + return FALSE; + } + } + + return TRUE; +} + +gboolean +nm_ap_utils_complete_connection (const GByteArray *ap_ssid, + const guint8 ap_bssid[ETH_ALEN], + NM80211Mode ap_mode, + guint32 ap_flags, + guint32 ap_wpa_flags, + guint32 ap_rsn_flags, + NMConnection *connection, + gboolean lock_bssid, + GError **error) +{ + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x *s_8021x; + const GByteArray *ssid; + const char *mode, *key_mgmt, *auth_alg, *leap_username; + gboolean adhoc = FALSE; + + s_wifi = nm_connection_get_setting_wireless (connection); + g_assert (s_wifi); + s_wsec = nm_connection_get_setting_wireless_security (connection); + s_8021x = nm_connection_get_setting_802_1x (connection); + + /* Fill in missing SSID */ + ssid = nm_setting_wireless_get_ssid (s_wifi); + if (!ssid) + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_SSID, ap_ssid, NULL); + else if ( ssid->len != ap_ssid->len + || memcmp (ssid->data, ap_ssid->data, ssid->len)) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_ERROR, + NM_SETTING_WIRELESS_ERROR_INVALID_PROPERTY, + "Setting SSID did not match AP SSID"); + return FALSE; + } + + if (lock_bssid && !nm_setting_wireless_get_bssid (s_wifi)) { + GByteArray *bssid; + + bssid = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (bssid, ap_bssid, ETH_ALEN); + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_BSSID, bssid, NULL); + g_byte_array_free (bssid, TRUE); + } + + /* And mode */ + mode = nm_setting_wireless_get_mode (s_wifi); + if (mode) { + gboolean valid = FALSE; + + /* Make sure the supplied mode matches the AP's */ + if ( !strcmp (mode, NM_SETTING_WIRELESS_MODE_INFRA) + || !strcmp (mode, NM_SETTING_WIRELESS_MODE_AP)) { + if (ap_mode == NM_802_11_MODE_INFRA) + valid = TRUE; + } else if (!strcmp (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { + if (ap_mode == NM_802_11_MODE_ADHOC) + valid = TRUE; + adhoc = TRUE; + } + + if (valid == FALSE) { + g_set_error (error, + NM_SETTING_WIRELESS_ERROR, + NM_SETTING_WIRELESS_ERROR_INVALID_PROPERTY, + NM_SETTING_WIRELESS_MODE); + return FALSE; + } + } else { + mode = NM_SETTING_WIRELESS_MODE_INFRA; + if (ap_mode == NM_802_11_MODE_ADHOC) { + mode = NM_SETTING_WIRELESS_MODE_ADHOC; + adhoc = TRUE; + } + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_MODE, mode, NULL); + } + + /* Security */ + + /* Open */ + if ( !(ap_flags & NM_802_11_AP_FLAGS_PRIVACY) + && (ap_wpa_flags == NM_802_11_AP_SEC_NONE) + && (ap_rsn_flags == NM_802_11_AP_SEC_NONE)) { + /* Make sure the connection doesn't specify security */ + if (s_wsec || s_8021x) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "AP is unencrypted but setting specifies security"); + return FALSE; + } + return TRUE; + } + + /* Everything else requires security */ + if (!s_wsec) { + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + } + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + leap_username = nm_setting_wireless_security_get_leap_username (s_wsec); + + /* Ad-Hoc checks */ + if (!verify_adhoc (s_wsec, s_8021x, adhoc, error)) + return FALSE; + + /* Static WEP, Dynamic WEP, or LEAP */ + if ( (ap_flags & NM_802_11_AP_FLAGS_PRIVACY) + && (ap_wpa_flags == NM_802_11_AP_SEC_NONE) + && (ap_rsn_flags == NM_802_11_AP_SEC_NONE)) { + const char *tag = "WEP"; + gboolean is_dynamic_wep = FALSE; + + if (!verify_leap (s_wsec, s_8021x, adhoc, error)) + return FALSE; + + if (leap_username) { + tag = "LEAP"; + } else { + /* Static or Dynamic WEP */ + if (!verify_dynamic_wep (s_wsec, s_8021x, adhoc, error)) + return FALSE; + + if (s_8021x || (key_mgmt && !strcmp (key_mgmt, "ieee8021x"))) { + is_dynamic_wep = TRUE; + tag = "Dynamic WEP"; + } + } + + /* Nothing WPA-related can be set */ + if (!verify_no_wpa (s_wsec, tag, error)) + return FALSE; + + if (leap_username) { + /* LEAP */ + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", + NULL); + } else if (is_dynamic_wep) { + /* Dynamic WEP */ + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", + NULL); + + if (s_8021x) { + /* Dynamic WEP requires a valid 802.1x setting since we can't + * autocomplete 802.1x. + */ + if (!nm_setting_verify (NM_SETTING (s_8021x), NULL, error)) + return FALSE; + } + } else { + /* Static WEP */ + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", + NULL); + } + + return TRUE; + } + + /* WPA/RSN */ + g_assert (ap_wpa_flags || ap_rsn_flags); + + /* Ensure key management is valid for WPA */ + if ((key_mgmt && !strcmp (key_mgmt, "ieee8021x")) || leap_username) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA incompatible with non-EAP (original) LEAP or Dynamic WEP"); + return FALSE; + } + + /* 'shared' auth incompatible with any type of WPA */ + if (auth_alg && strcmp (auth_alg, "open")) { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "WPA incompatible with Shared Key authentication"); + return FALSE; + } + + if (!verify_no_wep (s_wsec, "WPA", error)) + return FALSE; + + if (!verify_wpa_psk (s_wsec, s_8021x, adhoc, ap_wpa_flags, ap_rsn_flags, error)) + return FALSE; + + if (!adhoc && !verify_wpa_eap (s_wsec, s_8021x, ap_wpa_flags, ap_rsn_flags, error)) + return FALSE; + + if (adhoc) { + g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-none", NULL); + /* Ad-Hoc does not support RSN/WPA2 */ + nm_setting_wireless_security_add_proto (s_wsec, "wpa"); + nm_setting_wireless_security_add_pairwise (s_wsec, "none"); + nm_setting_wireless_security_add_group (s_wsec, "tkip"); + } else if (s_8021x) { + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", + NULL); + /* Leave proto/pairwise/group as client set them; if they are unset the + * supplicant will figure out the best combination at connect time. + */ + + /* 802.1x also requires the client to completely fill in the 8021x + * setting. Since there's so much configuration required for it, there's + * no way it can be automatically completed. + */ + } else if ( (key_mgmt && !strcmp (key_mgmt, "wpa-psk")) + || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) + || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) { + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", + NULL); + /* Leave proto/pairwise/group as client set them; if they are unset the + * supplicant will figure out the best combination at connect time. + */ + } else { + g_set_error_literal (error, + NM_SETTING_WIRELESS_SECURITY_ERROR, + NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY, + "Failed to determine AP security information"); + return FALSE; + } + + return TRUE; +} + +guint32 +nm_ap_utils_level_to_quality (gint val) +{ + if (val < 0) { + /* Assume dBm already; rough conversion: best = -40, worst = -100 */ + val = abs (CLAMP (val, -100, -40) + 40); /* normalize to 0 */ + val = 100 - (int) ((100.0 * (double) val) / 60.0); + } else if (val > 110 && val < 256) { + /* assume old-style WEXT 8-bit unsigned signal level */ + val -= 256; /* subtract 256 to convert to dBm */ + val = abs (CLAMP (val, -100, -40) + 40); /* normalize to 0 */ + val = 100 - (int) ((100.0 * (double) val) / 60.0); + } else { + /* Assume signal is a "quality" percentage */ + val = CLAMP (val, 0, 100); + } + g_assert (val >= 0); + + return (guint32) val; +} + diff --git a/src/devices/wifi/nm-wifi-ap-utils.h b/src/devices/wifi/nm-wifi-ap-utils.h new file mode 100644 index 000000000..992b839d5 --- /dev/null +++ b/src/devices/wifi/nm-wifi-ap-utils.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2011 Red Hat, Inc. + */ + +#ifndef NM_WIFI_AP_UTILS_H +#define NM_WIFI_AP_UTILS_H + +#include <net/ethernet.h> + +#include <NetworkManager.h> +#include <nm-connection.h> +#include <nm-setting-wireless.h> +#include <nm-setting-wireless-security.h> +#include <nm-setting-8021x.h> + +gboolean nm_ap_utils_complete_connection (const GByteArray *ssid, + const guint8 bssid[ETH_ALEN], + NM80211Mode mode, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + NMConnection *connection, + gboolean lock_bssid, + GError **error); + +guint32 nm_ap_utils_level_to_quality (gint val); + +#endif /* NM_WIFI_AP_UTILS_H */ + diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c new file mode 100644 index 000000000..363be2e32 --- /dev/null +++ b/src/devices/wifi/nm-wifi-ap.c @@ -0,0 +1,1293 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2004 - 2011 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include <string.h> +#include <stdlib.h> +#include <netinet/ether.h> + +#include "nm-wifi-ap.h" +#include "nm-wifi-ap-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-utils.h" +#include "nm-logging.h" +#include "nm-dbus-manager.h" + +#include "nm-setting-wireless.h" +#include "nm-glib-compat.h" + +#include "nm-access-point-glue.h" + +/* + * Encapsulates Access Point information + */ +typedef struct +{ + char *dbus_path; + char *supplicant_path; /* D-Bus object path of this AP from wpa_supplicant */ + + /* Scanned or cached values */ + GByteArray * ssid; + struct ether_addr address; + NM80211Mode mode; + gint8 strength; + guint32 freq; /* Frequency in MHz; ie 2412 (== 2.412 GHz) */ + guint32 max_bitrate;/* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */ + + NM80211ApFlags flags; /* General flags */ + NM80211ApSecurityFlags wpa_flags; /* WPA-related flags */ + NM80211ApSecurityFlags rsn_flags; /* RSN (WPA2) -related flags */ + + /* Non-scanned attributes */ + gboolean fake; /* Whether or not the AP is from a scan */ + gboolean hotspot; /* Whether the AP is a local device's hotspot network */ + gboolean broadcast; /* Whether or not the AP is broadcasting (hidden) */ + gint32 last_seen; /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */ +} NMAccessPointPrivate; + +#define NM_AP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_AP, NMAccessPointPrivate)) + +G_DEFINE_TYPE (NMAccessPoint, nm_ap, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_FLAGS, + PROP_WPA_FLAGS, + PROP_RSN_FLAGS, + PROP_SSID, + PROP_FREQUENCY, + PROP_HW_ADDRESS, + PROP_MODE, + PROP_MAX_BITRATE, + PROP_STRENGTH, + LAST_PROP +}; + +static void +nm_ap_init (NMAccessPoint *ap) +{ + NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE (ap); + + priv->dbus_path = NULL; + priv->mode = NM_802_11_MODE_INFRA; + priv->flags = NM_802_11_AP_FLAGS_NONE; + priv->wpa_flags = NM_802_11_AP_SEC_NONE; + priv->rsn_flags = NM_802_11_AP_SEC_NONE; + priv->broadcast = TRUE; +} + +static void +finalize (GObject *object) +{ + NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE (object); + + g_free (priv->dbus_path); + g_free (priv->supplicant_path); + if (priv->ssid) + g_byte_array_free (priv->ssid, TRUE); + + G_OBJECT_CLASS (nm_ap_parent_class)->finalize (object); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMAccessPoint *ap = NM_AP (object); + + switch (prop_id) { + case PROP_FLAGS: + nm_ap_set_flags (ap, g_value_get_uint (value)); + break; + case PROP_WPA_FLAGS: + nm_ap_set_wpa_flags (ap, g_value_get_uint (value)); + break; + case PROP_RSN_FLAGS: + nm_ap_set_rsn_flags (ap, g_value_get_uint (value)); + break; + case PROP_SSID: + nm_ap_set_ssid (ap, (GByteArray *) g_value_get_boxed (value)); + break; + case PROP_FREQUENCY: + nm_ap_set_freq (ap, g_value_get_uint (value)); + break; + case PROP_MODE: + nm_ap_set_mode (ap, g_value_get_uint (value)); + break; + case PROP_MAX_BITRATE: + nm_ap_set_max_bitrate (ap, g_value_get_uint (value)); + break; + case PROP_STRENGTH: + nm_ap_set_strength (ap, g_value_get_schar (value)); + break; + case PROP_HW_ADDRESS: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE (object); + GArray * ssid; + int len; + int i; + + switch (prop_id) { + case PROP_FLAGS: + g_value_set_uint (value, priv->flags); + break; + case PROP_WPA_FLAGS: + g_value_set_uint (value, priv->wpa_flags); + break; + case PROP_RSN_FLAGS: + g_value_set_uint (value, priv->rsn_flags); + break; + case PROP_SSID: + len = priv->ssid ? priv->ssid->len : 0; + ssid = g_array_sized_new (FALSE, TRUE, sizeof (unsigned char), len); + for (i = 0; i < len; i++) + g_array_append_val (ssid, priv->ssid->data[i]); + g_value_set_boxed (value, ssid); + g_array_free (ssid, TRUE); + break; + case PROP_FREQUENCY: + g_value_set_uint (value, priv->freq); + break; + case PROP_HW_ADDRESS: + g_value_take_string (value, nm_utils_hwaddr_ntoa (&priv->address, ARPHRD_ETHER)); + break; + case PROP_MODE: + g_value_set_uint (value, priv->mode); + break; + case PROP_MAX_BITRATE: + g_value_set_uint (value, priv->max_bitrate); + break; + case PROP_STRENGTH: + g_value_set_schar (value, priv->strength); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_ap_class_init (NMAccessPointClass *ap_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (ap_class); + const NM80211ApSecurityFlags all_sec_flags = NM_802_11_AP_SEC_NONE + | NM_802_11_AP_SEC_PAIR_WEP40 + | NM_802_11_AP_SEC_PAIR_WEP104 + | NM_802_11_AP_SEC_PAIR_TKIP + | NM_802_11_AP_SEC_PAIR_CCMP + | NM_802_11_AP_SEC_GROUP_WEP40 + | NM_802_11_AP_SEC_GROUP_WEP104 + | NM_802_11_AP_SEC_GROUP_TKIP + | NM_802_11_AP_SEC_GROUP_CCMP + | NM_802_11_AP_SEC_KEY_MGMT_PSK + | NM_802_11_AP_SEC_KEY_MGMT_802_1X; + + g_type_class_add_private (ap_class, sizeof (NMAccessPointPrivate)); + + /* virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + /* properties */ + g_object_class_install_property + (object_class, PROP_FLAGS, + g_param_spec_uint (NM_AP_FLAGS, + "Flags", + "Flags", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_FLAGS_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_WPA_FLAGS, + g_param_spec_uint (NM_AP_WPA_FLAGS, + "WPA Flags", + "WPA Flags", + NM_802_11_AP_SEC_NONE, + all_sec_flags, + NM_802_11_AP_SEC_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_RSN_FLAGS, + g_param_spec_uint (NM_AP_RSN_FLAGS, + "RSN Flags", + "RSN Flags", + NM_802_11_AP_SEC_NONE, + all_sec_flags, + NM_802_11_AP_SEC_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_SSID, + g_param_spec_boxed (NM_AP_SSID, + "SSID", + "SSID", + DBUS_TYPE_G_UCHAR_ARRAY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_FREQUENCY, + g_param_spec_uint (NM_AP_FREQUENCY, + "Frequency", + "Frequency", + 0, 10000, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_HW_ADDRESS, + g_param_spec_string (NM_AP_HW_ADDRESS, + "MAC Address", + "Hardware MAC address", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_MODE, + g_param_spec_uint (NM_AP_MODE, + "Mode", + "Mode", + NM_802_11_MODE_ADHOC, NM_802_11_MODE_INFRA, NM_802_11_MODE_INFRA, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_MAX_BITRATE, + g_param_spec_uint (NM_AP_MAX_BITRATE, + "Max Bitrate", + "Max Bitrate", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_STRENGTH, + g_param_spec_char (NM_AP_STRENGTH, + "Strength", + "Strength", + G_MININT8, G_MAXINT8, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (ap_class), + &dbus_glib_nm_access_point_object_info); +} + +void +nm_ap_export_to_dbus (NMAccessPoint *ap) +{ + NMAccessPointPrivate *priv; + static guint32 counter = 0; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + + if (priv->dbus_path) { + nm_log_err (LOGD_CORE, "Tried to export AP %s twice.", priv->dbus_path); + return; + } + + priv->dbus_path = g_strdup_printf (NM_DBUS_PATH_ACCESS_POINT "/%d", counter++); + nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->dbus_path, ap); +} + +/* + * nm_ap_new + * + * Create a new, blank user access point info structure + * + */ +static NMAccessPoint * +nm_ap_new (void) +{ + return (NMAccessPoint *) g_object_new (NM_TYPE_AP, NULL); +} + +static NM80211ApSecurityFlags +pair_to_flags (const char *str) +{ + g_return_val_if_fail (str != NULL, NM_802_11_AP_SEC_NONE); + + if (strcmp (str, "tkip") == 0) + return NM_802_11_AP_SEC_PAIR_TKIP; + if (strcmp (str, "ccmp") == 0) + return NM_802_11_AP_SEC_PAIR_CCMP; + return NM_802_11_AP_SEC_NONE; +} + +static NM80211ApSecurityFlags +group_to_flags (const char *str) +{ + g_return_val_if_fail (str != NULL, NM_802_11_AP_SEC_NONE); + + if (strcmp (str, "wep40") == 0) + return NM_802_11_AP_SEC_GROUP_WEP40; + if (strcmp (str, "wep104") == 0) + return NM_802_11_AP_SEC_GROUP_WEP104; + if (strcmp (str, "tkip") == 0) + return NM_802_11_AP_SEC_GROUP_TKIP; + if (strcmp (str, "ccmp") == 0) + return NM_802_11_AP_SEC_GROUP_CCMP; + return NM_802_11_AP_SEC_NONE; +} + +static NM80211ApSecurityFlags +security_from_dict (GHashTable *security) +{ + GValue *value; + NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE; + const char **items, **iter; + + value = g_hash_table_lookup (security, "KeyMgmt"); + if (value) { + items = g_value_get_boxed (value); + for (iter = items; iter && *iter; iter++) { + if (strcmp (*iter, "wpa-psk") == 0) + flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (strcmp (*iter, "wpa-eap") == 0) + flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X; + } + } + + value = g_hash_table_lookup (security, "Pairwise"); + if (value) { + items = g_value_get_boxed (value); + for (iter = items; iter && *iter; iter++) + flags |= pair_to_flags (*iter); + } + + value = g_hash_table_lookup (security, "Group"); + if (value) + flags |= group_to_flags (g_value_get_string (value)); + + return flags; +} + +static void +foreach_property_cb (gpointer key, gpointer value, gpointer user_data) +{ + GValue *variant = (GValue *) value; + NMAccessPoint *ap = (NMAccessPoint *) user_data; + + if (G_VALUE_HOLDS_BOXED (variant)) { + GArray *array = g_value_get_boxed (variant); + + if (!strcmp (key, "SSID")) { + guint32 len = MIN (32, array->len); + GByteArray *ssid; + + /* Stupid ieee80211 layer uses <hidden> */ + if (((len == 8) || (len == 9)) + && (memcmp (array->data, "<hidden>", 8) == 0)) + return; + + if (nm_utils_is_empty_ssid ((const guint8 *) array->data, len)) + return; + + ssid = g_byte_array_sized_new (len); + g_byte_array_append (ssid, (const guint8 *) array->data, len); + nm_ap_set_ssid (ap, ssid); + g_byte_array_free (ssid, TRUE); + } else if (!strcmp (key, "BSSID")) { + struct ether_addr addr; + + if (array->len != ETH_ALEN) + return; + memset (&addr, 0, sizeof (struct ether_addr)); + memcpy (&addr, array->data, ETH_ALEN); + nm_ap_set_address (ap, &addr); + } else if (!strcmp (key, "Rates")) { + guint32 maxrate = 0; + int i; + + /* Find the max AP rate */ + for (i = 0; i < array->len; i++) { + guint32 r = g_array_index (array, guint32, i); + + if (r > maxrate) { + maxrate = r; + nm_ap_set_max_bitrate (ap, r / 1000); + } + } + } else if (!strcmp (key, "WPA")) { + NM80211ApSecurityFlags flags = nm_ap_get_wpa_flags (ap); + + flags |= security_from_dict (g_value_get_boxed (variant)); + nm_ap_set_wpa_flags (ap, flags); + } else if (!strcmp (key, "RSN")) { + NM80211ApSecurityFlags flags = nm_ap_get_rsn_flags (ap); + + flags |= security_from_dict (g_value_get_boxed (variant)); + nm_ap_set_rsn_flags (ap, flags); + } + } else if (G_VALUE_HOLDS_UINT (variant)) { + guint32 val = g_value_get_uint (variant); + + if (!strcmp (key, "Frequency")) + nm_ap_set_freq (ap, val); + } else if (G_VALUE_HOLDS_INT (variant)) { + gint val = g_value_get_int (variant); + + if (!strcmp (key, "Signal")) + nm_ap_set_strength (ap, nm_ap_utils_level_to_quality (val)); + } else if (G_VALUE_HOLDS_STRING (variant)) { + const char *val = g_value_get_string (variant); + + if (val && !strcmp (key, "Mode")) { + if (strcmp (val, "infrastructure") == 0) + nm_ap_set_mode (ap, NM_802_11_MODE_INFRA); + else if (strcmp (val, "ad-hoc") == 0) + nm_ap_set_mode (ap, NM_802_11_MODE_ADHOC); + } + } else if (G_VALUE_HOLDS_BOOLEAN (variant)) { + gboolean val = g_value_get_boolean (variant); + + if (strcmp (key, "Privacy") == 0) { + if (val) { + NM80211ApFlags flags = nm_ap_get_flags (ap); + nm_ap_set_flags (ap, flags | NM_802_11_AP_FLAGS_PRIVACY); + } + } + } +} + +NMAccessPoint * +nm_ap_new_from_properties (const char *supplicant_path, GHashTable *properties) +{ + NMAccessPoint *ap; + const struct ether_addr * addr; + const char bad_bssid1[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + const char bad_bssid2[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + g_return_val_if_fail (properties != NULL, NULL); + + ap = nm_ap_new (); + + g_object_freeze_notify (G_OBJECT (ap)); + g_hash_table_foreach (properties, foreach_property_cb, ap); + + nm_ap_set_supplicant_path (ap, supplicant_path); + + /* ignore APs with invalid BSSIDs */ + addr = nm_ap_get_address (ap); + if ( !(memcmp (addr->ether_addr_octet, bad_bssid1, ETH_ALEN)) + || !(memcmp (addr->ether_addr_octet, bad_bssid2, ETH_ALEN))) { + g_object_unref (ap); + return NULL; + } + + nm_ap_set_last_seen (ap, nm_utils_get_monotonic_timestamp_s ()); + + if (!nm_ap_get_ssid (ap)) + nm_ap_set_broadcast (ap, FALSE); + + g_object_thaw_notify (G_OBJECT (ap)); + + return ap; +} + +#define PROTO_WPA "wpa" +#define PROTO_RSN "rsn" + +static gboolean +has_proto (NMSettingWirelessSecurity *sec, const char *proto) +{ + guint32 num_protos = nm_setting_wireless_security_get_num_protos (sec); + guint32 i; + + if (num_protos == 0) + return TRUE; /* interpret no protos as "all" */ + + for (i = 0; i < num_protos; i++) { + if (!strcmp (nm_setting_wireless_security_get_proto (sec, i), proto)) + return TRUE; + } + return FALSE; +} + +static void +add_pair_ciphers (NMAccessPoint *ap, NMSettingWirelessSecurity *sec) +{ + guint32 num = nm_setting_wireless_security_get_num_pairwise (sec); + NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE; + guint32 i; + + /* If no ciphers are specified, that means "all" WPA ciphers */ + if (num == 0) { + flags |= NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP; + } else { + for (i = 0; i < num; i++) { + const char *cipher = nm_setting_wireless_security_get_pairwise (sec, i); + + if (!strcmp (cipher, "tkip")) + flags |= NM_802_11_AP_SEC_PAIR_TKIP; + else if (!strcmp (cipher, "ccmp")) + flags |= NM_802_11_AP_SEC_PAIR_CCMP; + } + } + + if (has_proto (sec, PROTO_WPA)) + nm_ap_set_wpa_flags (ap, nm_ap_get_wpa_flags (ap) | flags); + if (has_proto (sec, PROTO_RSN)) + nm_ap_set_rsn_flags (ap, nm_ap_get_rsn_flags (ap) | flags); +} + +static void +add_group_ciphers (NMAccessPoint *ap, NMSettingWirelessSecurity *sec) +{ + guint32 num = nm_setting_wireless_security_get_num_groups (sec); + NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE; + guint32 i; + + /* If no ciphers are specified, that means "all" WPA ciphers */ + if (num == 0) { + flags |= NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_GROUP_CCMP; + } else { + for (i = 0; i < num; i++) { + const char *cipher = nm_setting_wireless_security_get_group (sec, i); + + if (!strcmp (cipher, "wep40")) + flags |= NM_802_11_AP_SEC_GROUP_WEP40; + else if (!strcmp (cipher, "wep104")) + flags |= NM_802_11_AP_SEC_GROUP_WEP104; + else if (!strcmp (cipher, "tkip")) + flags |= NM_802_11_AP_SEC_GROUP_TKIP; + else if (!strcmp (cipher, "ccmp")) + flags |= NM_802_11_AP_SEC_GROUP_CCMP; + } + } + + if (has_proto (sec, PROTO_WPA)) + nm_ap_set_wpa_flags (ap, nm_ap_get_wpa_flags (ap) | flags); + if (has_proto (sec, PROTO_RSN)) + nm_ap_set_rsn_flags (ap, nm_ap_get_rsn_flags (ap) | flags); +} + +NMAccessPoint * +nm_ap_new_fake_from_connection (NMConnection *connection) +{ + NMAccessPoint *ap; + NMSettingWireless *s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + const GByteArray *ssid; + const char *mode, *band, *key_mgmt; + guint32 channel; + NM80211ApSecurityFlags flags; + gboolean psk = FALSE, eap = FALSE; + + g_return_val_if_fail (connection != NULL, NULL); + + s_wireless = nm_connection_get_setting_wireless (connection); + g_return_val_if_fail (s_wireless != NULL, NULL); + + ssid = nm_setting_wireless_get_ssid (s_wireless); + g_return_val_if_fail (ssid != NULL, NULL); + g_return_val_if_fail (ssid->len > 0, NULL); + + ap = nm_ap_new (); + nm_ap_set_fake (ap, TRUE); + nm_ap_set_ssid (ap, ssid); + + // FIXME: bssid too? + + mode = nm_setting_wireless_get_mode (s_wireless); + if (mode) { + if (!strcmp (mode, "infrastructure")) + nm_ap_set_mode (ap, NM_802_11_MODE_INFRA); + else if (!strcmp (mode, "adhoc")) + nm_ap_set_mode (ap, NM_802_11_MODE_ADHOC); + else if (!strcmp (mode, "ap")) { + nm_ap_set_mode (ap, NM_802_11_MODE_INFRA); + NM_AP_GET_PRIVATE (ap)->hotspot = TRUE; + } else + goto error; + } else { + nm_ap_set_mode (ap, NM_802_11_MODE_INFRA); + } + + band = nm_setting_wireless_get_band (s_wireless); + channel = nm_setting_wireless_get_channel (s_wireless); + + if (band && channel) { + guint32 freq = nm_utils_wifi_channel_to_freq (channel, band); + + if (freq == 0) + goto error; + + nm_ap_set_freq (ap, freq); + } + + s_wireless_sec = nm_connection_get_setting_wireless_security (connection); + /* Assume presence of a security setting means the AP is encrypted */ + if (!s_wireless_sec) + goto done; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wireless_sec); + + /* Everything below here uses encryption */ + nm_ap_set_flags (ap, nm_ap_get_flags (ap) | NM_802_11_AP_FLAGS_PRIVACY); + + /* Static & Dynamic WEP */ + if (!strcmp (key_mgmt, "none") || !strcmp (key_mgmt, "ieee8021x")) + goto done; + + psk = !strcmp (key_mgmt, "wpa-psk"); + eap = !strcmp (key_mgmt, "wpa-eap"); + if (psk || eap) { + if (has_proto (s_wireless_sec, PROTO_WPA)) { + flags = nm_ap_get_wpa_flags (ap); + flags |= eap ? NM_802_11_AP_SEC_KEY_MGMT_802_1X : NM_802_11_AP_SEC_KEY_MGMT_PSK; + nm_ap_set_wpa_flags (ap, flags); + } + if (has_proto (s_wireless_sec, PROTO_RSN)) { + flags = nm_ap_get_rsn_flags (ap); + flags |= eap ? NM_802_11_AP_SEC_KEY_MGMT_802_1X : NM_802_11_AP_SEC_KEY_MGMT_PSK; + nm_ap_set_rsn_flags (ap, flags); + } + + add_pair_ciphers (ap, s_wireless_sec); + add_group_ciphers (ap, s_wireless_sec); + } else if (!strcmp (key_mgmt, "wpa-none")) { + guint32 i; + + /* Ad-Hoc has special requirements: proto=WPA, pairwise=(none), and + * group=TKIP/CCMP (but not both). + */ + + flags = nm_ap_get_wpa_flags (ap); + flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK; + + /* Clear ciphers; pairwise must be unset anyway, and group gets set below */ + flags &= ~( NM_802_11_AP_SEC_PAIR_WEP40 + | NM_802_11_AP_SEC_PAIR_WEP104 + | NM_802_11_AP_SEC_PAIR_TKIP + | NM_802_11_AP_SEC_PAIR_CCMP + | NM_802_11_AP_SEC_GROUP_WEP40 + | NM_802_11_AP_SEC_GROUP_WEP104 + | NM_802_11_AP_SEC_GROUP_TKIP + | NM_802_11_AP_SEC_GROUP_CCMP); + + for (i = 0; i < nm_setting_wireless_security_get_num_groups (s_wireless_sec); i++) { + if (!strcmp (nm_setting_wireless_security_get_group (s_wireless_sec, i), "ccmp")) { + flags |= NM_802_11_AP_SEC_GROUP_CCMP; + break; + } + } + + /* Default to TKIP since not all WPA-capable cards can do CCMP */ + if (!(flags & NM_802_11_AP_SEC_GROUP_CCMP)) + flags |= NM_802_11_AP_SEC_GROUP_TKIP; + + nm_ap_set_wpa_flags (ap, flags); + + /* Don't use Ad-Hoc RSN yet */ + nm_ap_set_rsn_flags (ap, NM_802_11_AP_SEC_NONE); + } + +done: + return ap; + +error: + g_object_unref (ap); + return NULL; +} + + +#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" +#define MAC_ARG(x) ((guint8*)(x))[0],((guint8*)(x))[1],((guint8*)(x))[2],((guint8*)(x))[3],((guint8*)(x))[4],((guint8*)(x))[5] + +void +nm_ap_dump (NMAccessPoint *ap, const char *prefix) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + + nm_log_dbg (LOGD_WIFI_SCAN, "%s'%s' (%p)", + prefix, + priv->ssid ? nm_utils_escape_ssid (priv->ssid->data, priv->ssid->len) : "(none)", + ap); + nm_log_dbg (LOGD_WIFI_SCAN, " BSSID " MAC_FMT, MAC_ARG (priv->address.ether_addr_octet)); + nm_log_dbg (LOGD_WIFI_SCAN, " mode %d", priv->mode); + nm_log_dbg (LOGD_WIFI_SCAN, " flags 0x%X", priv->flags); + nm_log_dbg (LOGD_WIFI_SCAN, " wpa flags 0x%X", priv->wpa_flags); + nm_log_dbg (LOGD_WIFI_SCAN, " rsn flags 0x%X", priv->rsn_flags); + nm_log_dbg (LOGD_WIFI_SCAN, " quality %d", priv->strength); + nm_log_dbg (LOGD_WIFI_SCAN, " frequency %d", priv->freq); + nm_log_dbg (LOGD_WIFI_SCAN, " max rate %d", priv->max_bitrate); + nm_log_dbg (LOGD_WIFI_SCAN, " last-seen %d", (int) priv->last_seen); +} + +const char * +nm_ap_get_dbus_path (NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), NULL); + + return NM_AP_GET_PRIVATE (ap)->dbus_path; +} + +const char * +nm_ap_get_supplicant_path (NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), NULL); + + return NM_AP_GET_PRIVATE (ap)->supplicant_path; +} + +void +nm_ap_set_supplicant_path (NMAccessPoint *ap, const char *path) +{ + g_return_if_fail (NM_IS_AP (ap)); + g_return_if_fail (path != NULL); + + g_free (NM_AP_GET_PRIVATE (ap)->supplicant_path); + NM_AP_GET_PRIVATE (ap)->supplicant_path = g_strdup (path); +} + +/* + * Get/set functions for ssid + * + */ +const GByteArray * nm_ap_get_ssid (const NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), NULL); + + return NM_AP_GET_PRIVATE (ap)->ssid; +} + +void +nm_ap_set_ssid (NMAccessPoint *ap, const GByteArray * ssid) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + + if (ssid == priv->ssid) + return; + + /* same SSID */ + if ((ssid && priv->ssid) && (ssid->len == priv->ssid->len)) { + if (!memcmp (ssid->data, priv->ssid->data, ssid->len)) + return; + } + + if (priv->ssid) { + g_byte_array_free (priv->ssid, TRUE); + priv->ssid = NULL; + } + + if (ssid) { + /* Should never get zero-length SSIDs */ + g_warn_if_fail (ssid->len > 0); + + if (ssid->len) { + priv->ssid = g_byte_array_sized_new (ssid->len); + priv->ssid->len = ssid->len; + memcpy (priv->ssid->data, ssid->data, ssid->len); + } + } + + g_object_notify (G_OBJECT (ap), NM_AP_SSID); +} + + +NM80211ApFlags +nm_ap_get_flags (NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), NM_802_11_AP_SEC_NONE); + + return NM_AP_GET_PRIVATE (ap)->flags; +} + + +void +nm_ap_set_flags (NMAccessPoint *ap, NM80211ApFlags flags) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + + if (priv->flags != flags) { + priv->flags = flags; + g_object_notify (G_OBJECT (ap), NM_AP_FLAGS); + } +} + +NM80211ApSecurityFlags +nm_ap_get_wpa_flags (NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), NM_802_11_AP_SEC_NONE); + + return NM_AP_GET_PRIVATE (ap)->wpa_flags; +} + + +void +nm_ap_set_wpa_flags (NMAccessPoint *ap, NM80211ApSecurityFlags flags) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + if (priv->wpa_flags != flags) { + priv->wpa_flags = flags; + g_object_notify (G_OBJECT (ap), NM_AP_WPA_FLAGS); + } +} + +NM80211ApSecurityFlags +nm_ap_get_rsn_flags (NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), NM_802_11_AP_SEC_NONE); + + return NM_AP_GET_PRIVATE (ap)->rsn_flags; +} + + +void +nm_ap_set_rsn_flags (NMAccessPoint *ap, NM80211ApSecurityFlags flags) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + if (priv->rsn_flags != flags) { + priv->rsn_flags = flags; + g_object_notify (G_OBJECT (ap), NM_AP_RSN_FLAGS); + } +} + +/* + * Get/set functions for address + * + */ +const struct ether_addr * nm_ap_get_address (const NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), NULL); + + return &NM_AP_GET_PRIVATE (ap)->address; +} + +void nm_ap_set_address (NMAccessPoint *ap, const struct ether_addr * addr) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + g_return_if_fail (addr != NULL); + + priv = NM_AP_GET_PRIVATE (ap); + + if (memcmp (addr, &priv->address, sizeof (priv->address))) { + memcpy (&NM_AP_GET_PRIVATE (ap)->address, addr, sizeof (struct ether_addr)); + g_object_notify (G_OBJECT (ap), NM_AP_HW_ADDRESS); + } +} + + +/* + * Get/set functions for mode (ie Ad-Hoc, Infrastructure, etc) + * + */ +NM80211Mode nm_ap_get_mode (NMAccessPoint *ap) +{ + NM80211Mode mode; + + g_return_val_if_fail (NM_IS_AP (ap), -1); + + g_object_get (ap, NM_AP_MODE, &mode, NULL); + + return mode; +} + +void nm_ap_set_mode (NMAccessPoint *ap, const NM80211Mode mode) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + g_return_if_fail ( mode == NM_802_11_MODE_ADHOC + || mode == NM_802_11_MODE_INFRA); + + priv = NM_AP_GET_PRIVATE (ap); + + if (priv->mode != mode) { + priv->mode = mode; + g_object_notify (G_OBJECT (ap), NM_AP_MODE); + } +} + +gboolean +nm_ap_is_hotspot (NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), FALSE); + + return NM_AP_GET_PRIVATE (ap)->hotspot; +} + +/* + * Get/set functions for strength + * + */ +gint8 nm_ap_get_strength (NMAccessPoint *ap) +{ + gint8 strength; + + g_return_val_if_fail (NM_IS_AP (ap), 0); + + g_object_get (ap, NM_AP_STRENGTH, &strength, NULL); + + return strength; +} + +void nm_ap_set_strength (NMAccessPoint *ap, const gint8 strength) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + + if (priv->strength != strength) { + priv->strength = strength; + g_object_notify (G_OBJECT (ap), NM_AP_STRENGTH); + } +} + + +/* + * Get/set functions for frequency + * + */ +guint32 +nm_ap_get_freq (NMAccessPoint *ap) +{ + guint32 freq; + + g_return_val_if_fail (NM_IS_AP (ap), 0); + + g_object_get (ap, NM_AP_FREQUENCY, &freq, NULL); + + return freq; +} + +void +nm_ap_set_freq (NMAccessPoint *ap, + const guint32 freq) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + + if (priv->freq != freq) { + priv->freq = freq; + g_object_notify (G_OBJECT (ap), NM_AP_FREQUENCY); + } +} + + +/* + * Get/set functions for max bitrate (in kbit/s) + * + */ +guint32 nm_ap_get_max_bitrate (NMAccessPoint *ap) +{ + guint32 rate; + + g_return_val_if_fail (NM_IS_AP (ap), 0); + + g_object_get (ap, NM_AP_MAX_BITRATE, &rate, NULL); + + return rate; +} + +void +nm_ap_set_max_bitrate (NMAccessPoint *ap, guint32 bitrate) +{ + NMAccessPointPrivate *priv; + + g_return_if_fail (NM_IS_AP (ap)); + + priv = NM_AP_GET_PRIVATE (ap); + + if (priv->max_bitrate != bitrate) { + priv->max_bitrate = bitrate; + g_object_notify (G_OBJECT (ap), NM_AP_MAX_BITRATE); + } +} + +/* + * Get/Set functions to indicate that an access point is 'fake', ie whether + * or not it was created from scan results + */ +gboolean nm_ap_get_fake (const NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), FALSE); + + return NM_AP_GET_PRIVATE (ap)->fake; +} + +void nm_ap_set_fake (NMAccessPoint *ap, gboolean fake) +{ + g_return_if_fail (NM_IS_AP (ap)); + + NM_AP_GET_PRIVATE (ap)->fake = fake; +} + + +/* + * Get/Set functions to indicate whether an AP broadcasts its SSID. + */ +gboolean nm_ap_get_broadcast (NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), TRUE); + + return NM_AP_GET_PRIVATE (ap)->broadcast; +} + + +void nm_ap_set_broadcast (NMAccessPoint *ap, gboolean broadcast) +{ + g_return_if_fail (NM_IS_AP (ap)); + + NM_AP_GET_PRIVATE (ap)->broadcast = broadcast; +} + + +/* + * Get/Set functions for how long ago the AP was last seen in a scan. + * APs older than a certain date are dropped from the list. + * + */ +gint32 +nm_ap_get_last_seen (const NMAccessPoint *ap) +{ + g_return_val_if_fail (NM_IS_AP (ap), FALSE); + + return NM_AP_GET_PRIVATE (ap)->last_seen; +} + +void +nm_ap_set_last_seen (NMAccessPoint *ap, gint32 last_seen) +{ + g_return_if_fail (NM_IS_AP (ap)); + + NM_AP_GET_PRIVATE (ap)->last_seen = last_seen; +} + +gboolean +nm_ap_check_compatible (NMAccessPoint *self, + NMConnection *connection) +{ + NMAccessPointPrivate *priv; + NMSettingWireless *s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + const char *mode; + const char *band; + const GByteArray *bssid; + guint32 channel; + + g_return_val_if_fail (NM_IS_AP (self), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + + priv = NM_AP_GET_PRIVATE (self); + + s_wireless = nm_connection_get_setting_wireless (connection); + if (s_wireless == NULL) + return FALSE; + + if (!nm_utils_same_ssid (nm_setting_wireless_get_ssid (s_wireless), priv->ssid, TRUE)) + return FALSE; + + bssid = nm_setting_wireless_get_bssid (s_wireless); + if (bssid && memcmp (bssid->data, &priv->address, ETH_ALEN)) + return FALSE; + + mode = nm_setting_wireless_get_mode (s_wireless); + if (mode) { + if (!strcmp (mode, "infrastructure") && (priv->mode != NM_802_11_MODE_INFRA)) + return FALSE; + if (!strcmp (mode, "adhoc") && (priv->mode != NM_802_11_MODE_ADHOC)) + return FALSE; + if ( !strcmp (mode, "ap") + && (priv->mode != NM_802_11_MODE_INFRA || priv->hotspot != TRUE)) + return FALSE; + } + + band = nm_setting_wireless_get_band (s_wireless); + if (band) { + if (!strcmp (band, "a")) { + if (priv->freq < 4915 || priv->freq > 5825) + return FALSE; + } else if (!strcmp (band, "bg")) { + if (priv->freq < 2412 || priv->freq > 2484) + return FALSE; + } + } + + channel = nm_setting_wireless_get_channel (s_wireless); + if (channel) { + guint32 ap_chan = nm_utils_wifi_freq_to_channel (priv->freq); + + if (channel != ap_chan) + return FALSE; + } + + s_wireless_sec = nm_connection_get_setting_wireless_security (connection); + + return nm_setting_wireless_ap_security_compatible (s_wireless, + s_wireless_sec, + nm_ap_get_flags (self), + nm_ap_get_wpa_flags (self), + nm_ap_get_rsn_flags (self), + nm_ap_get_mode (self)); +} + +gboolean +nm_ap_complete_connection (NMAccessPoint *self, + NMConnection *connection, + gboolean lock_bssid, + GError **error) +{ + NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE (self); + + g_return_val_if_fail (connection != NULL, FALSE); + + return nm_ap_utils_complete_connection (priv->ssid, + priv->address.ether_addr_octet, + priv->mode, + priv->flags, + priv->wpa_flags, + priv->rsn_flags, + connection, + lock_bssid, + error); +} + +static gboolean +capabilities_compatible (NM80211ApSecurityFlags a_flags, NM80211ApSecurityFlags b_flags) +{ + if (a_flags == b_flags) + return TRUE; + + /* Make sure there's a common key management method */ + if (!((a_flags & 0x300) & (b_flags & 0x300))) + return FALSE; + + /* Ensure common pairwise ciphers */ + if (!((a_flags & 0xF) & (b_flags & 0xF))) + return FALSE; + + /* Ensure common group ciphers */ + if (!((a_flags & 0xF0) & (b_flags & 0xF0))) + return FALSE; + + return TRUE; +} + +NMAccessPoint * +nm_ap_match_in_list (NMAccessPoint *find_ap, + GSList *ap_list, + gboolean strict_match) +{ + GSList *iter; + + g_return_val_if_fail (find_ap != NULL, NULL); + + for (iter = ap_list; iter; iter = g_slist_next (iter)) { + NMAccessPoint * list_ap = NM_AP (iter->data); + const GByteArray * list_ssid = nm_ap_get_ssid (list_ap); + const struct ether_addr * list_addr = nm_ap_get_address (list_ap); + + const GByteArray * find_ssid = nm_ap_get_ssid (find_ap); + const struct ether_addr * find_addr = nm_ap_get_address (find_ap); + + /* SSID match; if both APs are hiding their SSIDs, + * let matching continue on BSSID and other properties + */ + if ( (!list_ssid && find_ssid) + || (list_ssid && !find_ssid) + || !nm_utils_same_ssid (list_ssid, find_ssid, TRUE)) + continue; + + /* BSSID match */ + if ( (strict_match || nm_ethernet_address_is_valid (find_addr)) + && nm_ethernet_address_is_valid (list_addr) + && memcmp (list_addr->ether_addr_octet, + find_addr->ether_addr_octet, + ETH_ALEN) != 0) { + continue; + } + + /* mode match */ + if (nm_ap_get_mode (list_ap) != nm_ap_get_mode (find_ap)) + continue; + + /* Frequency match */ + if (nm_ap_get_freq (list_ap) != nm_ap_get_freq (find_ap)) + continue; + + /* AP flags */ + if (nm_ap_get_flags (list_ap) != nm_ap_get_flags (find_ap)) + continue; + + if (strict_match) { + if (nm_ap_get_wpa_flags (list_ap) != nm_ap_get_wpa_flags (find_ap)) + continue; + + if (nm_ap_get_rsn_flags (list_ap) != nm_ap_get_rsn_flags (find_ap)) + continue; + } else { + NM80211ApSecurityFlags list_wpa_flags = nm_ap_get_wpa_flags (list_ap); + NM80211ApSecurityFlags find_wpa_flags = nm_ap_get_wpa_flags (find_ap); + NM80211ApSecurityFlags list_rsn_flags = nm_ap_get_rsn_flags (list_ap); + NM80211ApSecurityFlags find_rsn_flags = nm_ap_get_rsn_flags (find_ap); + + /* Just ensure that there is overlap in the capabilities */ + if ( !capabilities_compatible (list_wpa_flags, find_wpa_flags) + && !capabilities_compatible (list_rsn_flags, find_rsn_flags)) + continue; + } + + return list_ap; + } + + return NULL; +} + diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h new file mode 100644 index 000000000..f51fa078e --- /dev/null +++ b/src/devices/wifi/nm-wifi-ap.h @@ -0,0 +1,121 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2004 - 2011 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef NM_ACCESS_POINT_H +#define NM_ACCESS_POINT_H + +#include <glib.h> +#include <glib-object.h> +#include "NetworkManager.h" +#include "nm-connection.h" + +#define NM_TYPE_AP (nm_ap_get_type ()) +#define NM_AP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_AP, NMAccessPoint)) +#define NM_AP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_AP, NMAccessPointClass)) +#define NM_IS_AP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_AP)) +#define NM_IS_AP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_AP)) +#define NM_AP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_AP, NMAccessPointClass)) + +#define NM_AP_FLAGS "flags" +#define NM_AP_WPA_FLAGS "wpa-flags" +#define NM_AP_RSN_FLAGS "rsn-flags" +#define NM_AP_SSID "ssid" +#define NM_AP_FREQUENCY "frequency" +#define NM_AP_HW_ADDRESS "hw-address" +#define NM_AP_MODE "mode" +#define NM_AP_MAX_BITRATE "max-bitrate" +#define NM_AP_STRENGTH "strength" + +typedef struct { + GObject parent; +} NMAccessPoint; + +typedef struct { + GObjectClass parent; + +} NMAccessPointClass; + +GType nm_ap_get_type (void); + +NMAccessPoint * nm_ap_new_from_properties (const char *supplicant_path, + GHashTable *properties); +NMAccessPoint * nm_ap_new_fake_from_connection (NMConnection *connection); +void nm_ap_export_to_dbus (NMAccessPoint *ap); + +const char * nm_ap_get_dbus_path (NMAccessPoint *ap); + +const char * nm_ap_get_supplicant_path (NMAccessPoint *ap); +void nm_ap_set_supplicant_path (NMAccessPoint *ap, + const char *path); + +const GByteArray * nm_ap_get_ssid (const NMAccessPoint * ap); +void nm_ap_set_ssid (NMAccessPoint * ap, const GByteArray * ssid); + +NM80211ApFlags nm_ap_get_flags (NMAccessPoint *ap); +void nm_ap_set_flags (NMAccessPoint *ap, NM80211ApFlags flags); + +NM80211ApSecurityFlags nm_ap_get_wpa_flags (NMAccessPoint *ap); +void nm_ap_set_wpa_flags (NMAccessPoint *ap, NM80211ApSecurityFlags flags); + +NM80211ApSecurityFlags nm_ap_get_rsn_flags (NMAccessPoint *ap); +void nm_ap_set_rsn_flags (NMAccessPoint *ap, NM80211ApSecurityFlags flags); + +const struct ether_addr * nm_ap_get_address (const NMAccessPoint *ap); +void nm_ap_set_address (NMAccessPoint *ap, const struct ether_addr *addr); + +NM80211Mode nm_ap_get_mode (NMAccessPoint *ap); +void nm_ap_set_mode (NMAccessPoint *ap, const NM80211Mode mode); + +gboolean nm_ap_is_hotspot (NMAccessPoint *ap); + +gint8 nm_ap_get_strength (NMAccessPoint *ap); +void nm_ap_set_strength (NMAccessPoint *ap, gint8 strength); + +guint32 nm_ap_get_freq (NMAccessPoint *ap); +void nm_ap_set_freq (NMAccessPoint *ap, guint32 freq); + +guint32 nm_ap_get_max_bitrate (NMAccessPoint *ap); +void nm_ap_set_max_bitrate (NMAccessPoint *ap, guint32 bitrate); + +gboolean nm_ap_get_fake (const NMAccessPoint *ap); +void nm_ap_set_fake (NMAccessPoint *ap, gboolean fake); + +gboolean nm_ap_get_broadcast (NMAccessPoint *ap); +void nm_ap_set_broadcast (NMAccessPoint *ap, gboolean broadcast); + +gint32 nm_ap_get_last_seen (const NMAccessPoint *ap); +void nm_ap_set_last_seen (NMAccessPoint *ap, gint32 last_seen); + +gboolean nm_ap_check_compatible (NMAccessPoint *self, + NMConnection *connection); + +gboolean nm_ap_complete_connection (NMAccessPoint *self, + NMConnection *connection, + gboolean lock_bssid, + GError **error); + +NMAccessPoint * nm_ap_match_in_list (NMAccessPoint *find_ap, + GSList *ap_list, + gboolean strict_match); + +void nm_ap_dump (NMAccessPoint *ap, const char *prefix); + +#endif /* NM_ACCESS_POINT_H */ diff --git a/src/devices/wifi/nm-wifi-enum-types.c b/src/devices/wifi/nm-wifi-enum-types.c new file mode 100644 index 000000000..e914cfd68 --- /dev/null +++ b/src/devices/wifi/nm-wifi-enum-types.c @@ -0,0 +1,58 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#include "nm-wifi-enum-types.h" + +#include "nm-device-wifi.h" +#include "nm-wifi-ap.h" +#include "nm-device-olpc-mesh.h" + +GType +nm_wifi_error_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { NM_WIFI_ERROR_CONNECTION_NOT_WIRELESS, "NM_WIFI_ERROR_CONNECTION_NOT_WIRELESS", "ConnectionNotWireless" }, + { NM_WIFI_ERROR_CONNECTION_INVALID, "NM_WIFI_ERROR_CONNECTION_INVALID", "ConnectionInvalid" }, + { NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE, "NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE", "ConnectionIncompatible" }, + { NM_WIFI_ERROR_ACCESS_POINT_NOT_FOUND, "NM_WIFI_ERROR_ACCESS_POINT_NOT_FOUND", "AccessPointNotFound" }, + { NM_WIFI_ERROR_SCAN_NOT_ALLOWED, "NM_WIFI_ERROR_SCAN_NOT_ALLOWED", "ScanNotAllowed" }, + { NM_WIFI_ERROR_AP_MODE_UNSUPPORTED, "NM_WIFI_ERROR_AP_MODE_UNSUPPORTED", "ApModeUnsupported" }, + { NM_WIFI_ERROR_ADHOC_MODE_UNSUPPORTED, "NM_WIFI_ERROR_ADHOC_MODE_UNSUPPORTED", "AdhocModeUnsupported" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("NMWifiError"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} +GType +nm_olpc_mesh_error_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { NM_OLPC_MESH_ERROR_CONNECTION_NOT_MESH, "NM_OLPC_MESH_ERROR_CONNECTION_NOT_MESH", "ConnectionNotMesh" }, + { NM_OLPC_MESH_ERROR_CONNECTION_INVALID, "NM_OLPC_MESH_ERROR_CONNECTION_INVALID", "ConnectionInvalid" }, + { NM_OLPC_MESH_ERROR_CONNECTION_INCOMPATIBLE, "NM_OLPC_MESH_ERROR_CONNECTION_INCOMPATIBLE", "ConnectionIncompatible" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("NMOlpcMeshError"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + + + diff --git a/src/devices/wifi/nm-wifi-enum-types.h b/src/devices/wifi/nm-wifi-enum-types.h new file mode 100644 index 000000000..d4dca5bf9 --- /dev/null +++ b/src/devices/wifi/nm-wifi-enum-types.h @@ -0,0 +1,21 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#ifndef __NM_WIFI_ENUM_TYPES_H__ +#define __NM_WIFI_ENUM_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS +GType nm_wifi_error_get_type (void) G_GNUC_CONST; +#define NM_TYPE_WIFI_ERROR (nm_wifi_error_get_type ()) +GType nm_olpc_mesh_error_get_type (void) G_GNUC_CONST; +#define NM_TYPE_OLPC_MESH_ERROR (nm_olpc_mesh_error_get_type ()) +G_END_DECLS + +#endif /* __NM_WIFI_ENUM_TYPES_H__ */ + + + diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c new file mode 100644 index 000000000..02ad93f46 --- /dev/null +++ b/src/devices/wifi/nm-wifi-factory.c @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2011 - 2014 Red Hat, Inc. + */ + +#include <gmodule.h> + +#include "nm-device-factory.h" +#include "nm-device-wifi.h" +#include "nm-device-olpc-mesh.h" +#include "nm-settings-connection.h" + +#define NM_TYPE_WIFI_FACTORY (nm_wifi_factory_get_type ()) +#define NM_WIFI_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_FACTORY, NMWifiFactory)) + +typedef struct { + GObject parent; +} NMWifiFactory; + +typedef struct { + GObjectClass parent; +} NMWifiFactoryClass; + +static GType nm_wifi_factory_get_type (void); + +static void device_factory_interface_init (NMDeviceFactory *factory_iface); + +G_DEFINE_TYPE_EXTENDED (NMWifiFactory, nm_wifi_factory, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_DEVICE_FACTORY, device_factory_interface_init)) + +/**************************************************************************/ + +#define PLUGIN_TYPE NM_DEVICE_TYPE_WIFI + +G_MODULE_EXPORT NMDeviceFactory * +nm_device_factory_create (GError **error) +{ + return (NMDeviceFactory *) g_object_new (NM_TYPE_WIFI_FACTORY, NULL); +} + +G_MODULE_EXPORT NMDeviceType +nm_device_factory_get_device_type (void) +{ + return PLUGIN_TYPE; +} + +/**************************************************************************/ + +static NMDevice * +new_link (NMDeviceFactory *factory, NMPlatformLink *plink, GError **error) +{ + if (plink->type == NM_LINK_TYPE_WIFI) + return nm_device_wifi_new (plink); + else if (plink->type == NM_LINK_TYPE_OLPC_MESH) + return nm_device_olpc_mesh_new (plink); + return NULL; +} + +static void +device_factory_interface_init (NMDeviceFactory *factory_iface) +{ + factory_iface->new_link = new_link; +} + +static void +nm_wifi_factory_init (NMWifiFactory *self) +{ +} + +static void +nm_wifi_factory_class_init (NMWifiFactoryClass *wf_class) +{ +} + diff --git a/src/devices/wifi/tests/Makefile.am b/src/devices/wifi/tests/Makefile.am new file mode 100644 index 000000000..2667c5f12 --- /dev/null +++ b/src/devices/wifi/tests/Makefile.am @@ -0,0 +1,28 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_srcdir)/libnm-util \ + -I$(top_builddir)/libnm-util \ + -I$(top_srcdir)/src/platform \ + -I$(top_srcdir)/src/logging \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/devices/wifi \ + -I$(top_builddir)/src \ + -DG_LOG_DOMAIN=\""NetworkManager-wifi"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) + +noinst_PROGRAMS = test-wifi-ap-utils + +test_wifi_ap_utils_SOURCES = \ + test-wifi-ap-utils.c \ + $(srcdir)/../nm-wifi-ap.c \ + $(srcdir)/../nm-wifi-ap.h \ + $(srcdir)/../nm-wifi-ap-utils.c \ + $(srcdir)/../nm-wifi-ap-utils.h + +test_wifi_ap_utils_LDADD = $(top_builddir)/src/libNetworkManager.la + +TESTS = test-wifi-ap-utils + diff --git a/src/devices/wifi/tests/Makefile.in b/src/devices/wifi/tests/Makefile.in new file mode 100644 index 000000000..c0e75eedf --- /dev/null +++ b/src/devices/wifi/tests/Makefile.in @@ -0,0 +1,891 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = test-wifi-ap-utils$(EXEEXT) +TESTS = test-wifi-ap-utils$(EXEEXT) +subdir = src/devices/wifi/tests +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/build-aux/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \ + $(top_srcdir)/m4/compiler_warnings.m4 \ + $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gnome-code-coverage.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_test_wifi_ap_utils_OBJECTS = test-wifi-ap-utils.$(OBJEXT) \ + nm-wifi-ap.$(OBJEXT) nm-wifi-ap-utils.$(OBJEXT) +test_wifi_ap_utils_OBJECTS = $(am_test_wifi_ap_utils_OBJECTS) +test_wifi_ap_utils_DEPENDENCIES = \ + $(top_builddir)/src/libNetworkManager.la +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(test_wifi_ap_utils_SOURCES) +DIST_SOURCES = $(test_wifi_ap_utils_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + fi; \ +} +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CKDB_PATH = @CKDB_PATH@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@ +DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SYS_DIR = @DBUS_SYS_DIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHCLIENT_PATH = @DHCLIENT_PATH@ +DHCPCD_PATH = @DHCPCD_PATH@ +DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@ +DLLTOOL = @DLLTOOL@ +DNSMASQ_PATH = @DNSMASQ_PATH@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MAKEFILE = @GLIB_MAKEFILE@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +IPTABLES_PATH = @IPTABLES_PATH@ +IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@ +IWMX_SDK_LIBS = @IWMX_SDK_LIBS@ +KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDL = @LIBDL@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBNDP_CFLAGS = @LIBNDP_CFLAGS@ +LIBNDP_LIBS = @LIBNDP_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@ +LIBSOUP_LIBS = @LIBSOUP_LIBS@ +LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@ +LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MM_GLIB_CFLAGS = @MM_GLIB_CFLAGS@ +MM_GLIB_LIBS = @MM_GLIB_LIBS@ +MOC = @MOC@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEWT_CFLAGS = @NEWT_CFLAGS@ +NEWT_LIBS = @NEWT_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NM_MAJOR_VERSION = @NM_MAJOR_VERSION@ +NM_MICRO_VERSION = @NM_MICRO_VERSION@ +NM_MINOR_VERSION = @NM_MINOR_VERSION@ +NM_MODIFY_SYSTEM_POLICY = @NM_MODIFY_SYSTEM_POLICY@ +NM_VERSION = @NM_VERSION@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POLKIT_CFLAGS = @POLKIT_CFLAGS@ +POLKIT_LIBS = @POLKIT_LIBS@ +POSUB = @POSUB@ +PPPD_PATH = @PPPD_PATH@ +PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@ +PPPOE_PATH = @PPPOE_PATH@ +QT_CFLAGS = @QT_CFLAGS@ +QT_LIBS = @QT_LIBS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@ +SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@ +SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@ +SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +SYSTEM_CA_PATH = @SYSTEM_CA_PATH@ +UDEV_BASE_DIR = @UDEV_BASE_DIR@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND_RULES = @VALGRIND_RULES@ +VAPIGEN = @VAPIGEN@ +VAPIGEN_MAKEFILE = @VAPIGEN_MAKEFILE@ +VAPIGEN_VAPIDIR = @VAPIGEN_VAPIDIR@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nmbinary = @nmbinary@ +nmconfdir = @nmconfdir@ +nmdatadir = @nmdatadir@ +nmrundir = @nmrundir@ +nmstatedir = @nmstatedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +with_dhclient = @with_dhclient@ +with_dhcpcd = @with_dhcpcd@ +with_netconfig = @with_netconfig@ +with_resolvconf = @with_resolvconf@ +with_valgrind = @with_valgrind@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_srcdir)/libnm-util \ + -I$(top_builddir)/libnm-util \ + -I$(top_srcdir)/src/platform \ + -I$(top_srcdir)/src/logging \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/devices/wifi \ + -I$(top_builddir)/src \ + -DG_LOG_DOMAIN=\""NetworkManager-wifi"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) + +test_wifi_ap_utils_SOURCES = \ + test-wifi-ap-utils.c \ + $(srcdir)/../nm-wifi-ap.c \ + $(srcdir)/../nm-wifi-ap.h \ + $(srcdir)/../nm-wifi-ap-utils.c \ + $(srcdir)/../nm-wifi-ap-utils.h + +test_wifi_ap_utils_LDADD = $(top_builddir)/src/libNetworkManager.la +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/devices/wifi/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/devices/wifi/tests/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +test-wifi-ap-utils$(EXEEXT): $(test_wifi_ap_utils_OBJECTS) $(test_wifi_ap_utils_DEPENDENCIES) $(EXTRA_test_wifi_ap_utils_DEPENDENCIES) + @rm -f test-wifi-ap-utils$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_wifi_ap_utils_OBJECTS) $(test_wifi_ap_utils_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wifi-ap-utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wifi-ap.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-wifi-ap-utils.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +nm-wifi-ap.o: $(srcdir)/../nm-wifi-ap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-wifi-ap.o -MD -MP -MF $(DEPDIR)/nm-wifi-ap.Tpo -c -o nm-wifi-ap.o `test -f '$(srcdir)/../nm-wifi-ap.c' || echo '$(srcdir)/'`$(srcdir)/../nm-wifi-ap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-wifi-ap.Tpo $(DEPDIR)/nm-wifi-ap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../nm-wifi-ap.c' object='nm-wifi-ap.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-wifi-ap.o `test -f '$(srcdir)/../nm-wifi-ap.c' || echo '$(srcdir)/'`$(srcdir)/../nm-wifi-ap.c + +nm-wifi-ap.obj: $(srcdir)/../nm-wifi-ap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-wifi-ap.obj -MD -MP -MF $(DEPDIR)/nm-wifi-ap.Tpo -c -o nm-wifi-ap.obj `if test -f '$(srcdir)/../nm-wifi-ap.c'; then $(CYGPATH_W) '$(srcdir)/../nm-wifi-ap.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../nm-wifi-ap.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-wifi-ap.Tpo $(DEPDIR)/nm-wifi-ap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../nm-wifi-ap.c' object='nm-wifi-ap.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-wifi-ap.obj `if test -f '$(srcdir)/../nm-wifi-ap.c'; then $(CYGPATH_W) '$(srcdir)/../nm-wifi-ap.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../nm-wifi-ap.c'; fi` + +nm-wifi-ap-utils.o: $(srcdir)/../nm-wifi-ap-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-wifi-ap-utils.o -MD -MP -MF $(DEPDIR)/nm-wifi-ap-utils.Tpo -c -o nm-wifi-ap-utils.o `test -f '$(srcdir)/../nm-wifi-ap-utils.c' || echo '$(srcdir)/'`$(srcdir)/../nm-wifi-ap-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-wifi-ap-utils.Tpo $(DEPDIR)/nm-wifi-ap-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../nm-wifi-ap-utils.c' object='nm-wifi-ap-utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-wifi-ap-utils.o `test -f '$(srcdir)/../nm-wifi-ap-utils.c' || echo '$(srcdir)/'`$(srcdir)/../nm-wifi-ap-utils.c + +nm-wifi-ap-utils.obj: $(srcdir)/../nm-wifi-ap-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-wifi-ap-utils.obj -MD -MP -MF $(DEPDIR)/nm-wifi-ap-utils.Tpo -c -o nm-wifi-ap-utils.obj `if test -f '$(srcdir)/../nm-wifi-ap-utils.c'; then $(CYGPATH_W) '$(srcdir)/../nm-wifi-ap-utils.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../nm-wifi-ap-utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-wifi-ap-utils.Tpo $(DEPDIR)/nm-wifi-ap-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../nm-wifi-ap-utils.c' object='nm-wifi-ap-utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-wifi-ap-utils.obj `if test -f '$(srcdir)/../nm-wifi-ap-utils.c'; then $(CYGPATH_W) '$(srcdir)/../nm-wifi-ap-utils.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../nm-wifi-ap-utils.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-TESTS check-am clean \ + clean-generic clean-libtool clean-noinstPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/devices/wifi/tests/test-wifi-ap-utils.c b/src/devices/wifi/tests/test-wifi-ap-utils.c new file mode 100644 index 000000000..36d58f2a8 --- /dev/null +++ b/src/devices/wifi/tests/test-wifi-ap-utils.c @@ -0,0 +1,1545 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2011 Red Hat, Inc. + * + */ + +#include <glib.h> +#include <string.h> + +#include "nm-wifi-ap-utils.h" +#include "nm-dbus-glib-types.h" + +#include "nm-setting-connection.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-8021x.h" + +#define DEBUG 1 + +/*******************************************/ + +#define COMPARE(src, expected, success, error, edomain, ecode) \ +{ \ + if (expected) { \ + if (!success) { \ + g_assert (error != NULL); \ + g_warning ("Failed to complete connection: (%d) %s", error->code, error->message); \ + } \ + g_assert (success == TRUE); \ + g_assert (error == NULL); \ +\ + success = nm_connection_compare (src, expected, NM_SETTING_COMPARE_FLAG_EXACT); \ + if (success == FALSE && DEBUG) { \ + g_message ("\n- COMPLETED ---------------------------------\n"); \ + nm_connection_dump (src); \ + g_message ("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \ + nm_connection_dump (expected); \ + g_message ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \ + } \ + g_assert (success == TRUE); \ + } else { \ + if (success) { \ + g_message ("\n- COMPLETED ---------------------------------\n"); \ + nm_connection_dump (src); \ + } \ + g_assert (success == FALSE); \ + g_assert_error (error, edomain, ecode); \ + } \ + \ + g_clear_error (&error); \ +} + +static gboolean +complete_connection (const char *ssid, + const guint8 bssid[ETH_ALEN], + NM80211Mode mode, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + gboolean lock_bssid, + NMConnection *src, + GError **error) +{ + GByteArray *tmp; + gboolean success; + NMSettingWireless *s_wifi; + + /* Add a wifi setting if one doesn't exist */ + s_wifi = nm_connection_get_setting_wireless (src); + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (src, NM_SETTING (s_wifi)); + } + + tmp = g_byte_array_sized_new (strlen (ssid)); + g_byte_array_append (tmp, (const guint8 *) ssid, strlen (ssid)); + + success = nm_ap_utils_complete_connection (tmp, + bssid, + mode, + flags, + wpa_flags, + rsn_flags, + src, + lock_bssid, + error); + g_byte_array_free (tmp, TRUE); + return success; +} + +typedef struct { + const char *key; + const char *str; + guint32 uint; +} KeyData; + +static void +set_items (NMSetting *setting, const KeyData *items) +{ + const KeyData *item; + GParamSpec *pspec; + GByteArray *tmp; + + for (item = items; item && item->key; item++) { + g_assert (item->key); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), item->key); + g_assert (pspec); + + if (pspec->value_type == G_TYPE_STRING) { + g_assert (item->uint == 0); + if (item->str) + g_object_set (G_OBJECT (setting), item->key, item->str, NULL); + } else if (pspec->value_type == G_TYPE_UINT) { + g_assert (item->str == NULL); + g_object_set (G_OBJECT (setting), item->key, item->uint, NULL); + } else if (pspec->value_type == G_TYPE_INT) { + gint foo = (gint) item->uint; + + g_assert (item->str == NULL); + g_object_set (G_OBJECT (setting), item->key, foo, NULL); + } else if (pspec->value_type == G_TYPE_BOOLEAN) { + gboolean foo = !! (item->uint); + + g_assert (item->str == NULL); + g_object_set (G_OBJECT (setting), item->key, foo, NULL); + } else if (pspec->value_type == DBUS_TYPE_G_UCHAR_ARRAY) { + g_assert (item->str); + tmp = g_byte_array_sized_new (strlen (item->str)); + g_byte_array_append (tmp, (const guint8 *) item->str, strlen (item->str)); + g_object_set (G_OBJECT (setting), item->key, tmp, NULL); + g_byte_array_free (tmp, TRUE); + } else { + /* Special types, check based on property name */ + if (!strcmp (item->key, NM_SETTING_WIRELESS_SECURITY_PROTO)) + nm_setting_wireless_security_add_proto (NM_SETTING_WIRELESS_SECURITY (setting), item->str); + else if (!strcmp (item->key, NM_SETTING_WIRELESS_SECURITY_PAIRWISE)) + nm_setting_wireless_security_add_pairwise (NM_SETTING_WIRELESS_SECURITY (setting), item->str); + else if (!strcmp (item->key, NM_SETTING_WIRELESS_SECURITY_GROUP)) + nm_setting_wireless_security_add_group (NM_SETTING_WIRELESS_SECURITY (setting), item->str); + else if (!strcmp (item->key, NM_SETTING_802_1X_EAP)) + nm_setting_802_1x_add_eap_method (NM_SETTING_802_1X (setting), item->str); + } + } +} + +static NMSettingWireless * +fill_wifi_empty (NMConnection *connection) +{ + NMSettingWireless *s_wifi; + + s_wifi = nm_connection_get_setting_wireless (connection); + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + } + return s_wifi; +} + +static NMSettingWireless * +fill_wifi (NMConnection *connection, const KeyData items[]) +{ + NMSettingWireless *s_wifi; + + s_wifi = nm_connection_get_setting_wireless (connection); + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + } + + set_items (NM_SETTING (s_wifi), items); + return s_wifi; +} + +static NMSettingWirelessSecurity * +fill_wsec (NMConnection *connection, const KeyData items[]) +{ + NMSettingWirelessSecurity *s_wsec; + + s_wsec = nm_connection_get_setting_wireless_security (connection); + if (!s_wsec) { + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + } + + set_items (NM_SETTING (s_wsec), items); + return s_wsec; +} + +static NMSetting8021x * +fill_8021x (NMConnection *connection, const KeyData items[]) +{ + NMSetting8021x *s_8021x; + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (!s_8021x) { + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_connection_add_setting (connection, NM_SETTING (s_8021x)); + } + + set_items (NM_SETTING (s_8021x), items); + return s_8021x; +} + +static NMConnection * +create_basic (const char *ssid, + const guint8 *bssid, + NM80211Mode mode) +{ + NMConnection *connection; + NMSettingWireless *s_wifi = NULL; + GByteArray *tmp; + + connection = nm_connection_new (); + + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + + /* SSID */ + tmp = g_byte_array_sized_new (strlen (ssid)); + g_byte_array_append (tmp, (const guint8 *) ssid, strlen (ssid)); + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_SSID, tmp, NULL); + g_byte_array_free (tmp, TRUE); + + /* BSSID */ + if (bssid) { + tmp = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (tmp, bssid, ETH_ALEN); + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_BSSID, tmp, NULL); + g_byte_array_free (tmp, TRUE); + } + + if (mode == NM_802_11_MODE_INFRA) + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_MODE, "infrastructure", NULL); + else if (mode == NM_802_11_MODE_ADHOC) + g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_MODE, "adhoc", NULL); + else + g_assert_not_reached (); + + return connection; +} + +/*******************************************/ + +static void +test_lock_bssid (void) +{ + NMConnection *src, *expected; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const char *ssid = "blahblah"; + gboolean success; + GError *error = NULL; + + src = nm_connection_new (); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + TRUE, + src, &error); + expected = create_basic (ssid, bssid, NM_802_11_MODE_INFRA); + COMPARE (src, expected, success, error, 0, 0); + + g_object_unref (src); + g_object_unref (expected); +} + +/*******************************************/ + +static void +test_open_ap_empty_connection (void) +{ + NMConnection *src, *expected; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const char *ssid = "blahblah"; + gboolean success; + GError *error = NULL; + + /* Test that an empty source connection is correctly filled with the + * SSID and Infra modes of the given AP details. + */ + + src = nm_connection_new (); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + expected = create_basic (ssid, NULL, NM_802_11_MODE_INFRA); + COMPARE (src, expected, success, error, 0, 0); + + g_object_unref (src); + g_object_unref (expected); +} + +/*******************************************/ + +static void +test_open_ap_leap_connection_1 (gconstpointer add_wifi) +{ + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { { NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, "Bill Smith", 0 }, { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that a basic connection filled with a LEAP username is + * rejected when completion is attempted with an open AP. LEAP requires + * the AP to have the Privacy bit set. + */ + + src = nm_connection_new (); + if (add_wifi) + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + + success = complete_connection ("blahblah", bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + /* We expect failure */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_open_ap_leap_connection_2 (void) +{ + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that a basic connection specifying IEEE8021x security (ie, Dynamic + * WEP or LEAP) is rejected when completion is attempted with an open AP. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + + success = complete_connection ("blahblah", bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + /* We expect failure */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_open_ap_wep_connection (gconstpointer add_wifi) +{ + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, "11111111111111111111111111", 0 }, + { NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, NULL, 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that a static WEP connection is rejected when completion is + * attempted with an open AP. + */ + + src = nm_connection_new (); + if (add_wifi) + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + success = complete_connection ("blahblah", bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + /* We expect failure */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_ap_wpa_psk_connection_base (const char *key_mgmt, + const char *auth_alg, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + gboolean add_wifi, + NMConnection *expected) +{ + NMConnection *src; + const char *ssid = "blahblah"; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData exp_wifi[] = { + { NM_SETTING_WIRELESS_SSID, ssid, 0 }, + { NM_SETTING_WIRELESS_MODE, "infrastructure", 0 }, + { NULL } }; + const KeyData both_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt, 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0 }, + { NM_SETTING_WIRELESS_SECURITY_PSK, "asdfasdfasdfasdfasdfafs", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + src = nm_connection_new (); + if (add_wifi) + fill_wifi_empty (src); + fill_wsec (src, both_wsec); + success = complete_connection (ssid, bssid, NM_802_11_MODE_INFRA, + flags, wpa_flags, rsn_flags, + FALSE, src, &error); + if (expected) { + fill_wifi (expected, exp_wifi); + fill_wsec (expected, both_wsec); + } + COMPARE (src, expected, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +static void +test_open_ap_wpa_psk_connection_1 (void) +{ + /* Test that a WPA-PSK connection filling only the PSK itself and *not* + * filling the wifi setting is rejected when completion is attempted with + * an open AP. + */ + test_ap_wpa_psk_connection_base (NULL, NULL, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +static void +test_open_ap_wpa_psk_connection_2 (void) +{ + /* Test that a WPA-PSK connection filling only the PSK itself and also + * filling the wifi setting is rejected when completion is attempted with + * an open AP. + */ + test_ap_wpa_psk_connection_base (NULL, NULL, + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + TRUE, NULL); +} + +static void +test_open_ap_wpa_psk_connection_3 (void) +{ + /* Test that a WPA-PSK connection filling the PSK and setting the auth alg + * to 'open' is rejected when completion is attempted with an open AP. + */ + test_ap_wpa_psk_connection_base (NULL, "open", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +static void +test_open_ap_wpa_psk_connection_4 (void) +{ + /* Test that a WPA-PSK connection filling the PSK and setting the auth alg + * to 'shared' is rejected when completion is attempted with an open AP. + * Shared auth cannot be used with WPA. + */ + test_ap_wpa_psk_connection_base (NULL, "shared", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +static void +test_open_ap_wpa_psk_connection_5 (void) +{ + /* Test that a WPA-PSK connection filling the PSK, the auth algorithm, and + * key management is rejected when completion is attempted with an open AP. + */ + test_ap_wpa_psk_connection_base ("wpa-psk", "open", + NM_802_11_AP_FLAGS_NONE, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +/*******************************************/ + +static void +test_ap_wpa_eap_connection_base (const char *key_mgmt, + const char *auth_alg, + guint32 flags, + guint32 wpa_flags, + guint32 rsn_flags, + gboolean add_wifi, + guint error_domain, + guint error_code) +{ + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_empty[] = { { NULL } }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt, 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + src = nm_connection_new (); + if (add_wifi) + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + fill_8021x (src, src_empty); + success = complete_connection ("blahblah", bssid, NM_802_11_MODE_INFRA, + flags, wpa_flags, rsn_flags, + FALSE, src, &error); + /* Failure expected */ + COMPARE (src, NULL, success, error, error_domain, error_code); + + g_object_unref (src); +} + +enum { + IDX_NONE = 0, + IDX_OPEN, + IDX_PRIV, + IDX_WPA_PSK_PTKIP_GTKIP, + IDX_WPA_PSK_PTKIP_PCCMP_GTKIP, + IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP, + IDX_WPA_RSN_PSK_PCCMP_GCCMP, + IDX_RSN_PSK_PCCMP_GCCMP, + IDX_RSN_PSK_PTKIP_PCCMP_GTKIP, + IDX_WPA_8021X, + IDX_RSN_8021X, +}; + +static guint32 +flags_for_idx (guint32 idx) +{ + if (idx == IDX_OPEN) + return NM_802_11_AP_FLAGS_NONE; + else if ( idx == IDX_PRIV + || idx == IDX_WPA_PSK_PTKIP_GTKIP + || idx == IDX_WPA_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_RSN_PSK_PCCMP_GCCMP + || idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_WPA_RSN_PSK_PCCMP_GCCMP + || idx == IDX_WPA_8021X + || idx == IDX_RSN_8021X) + return NM_802_11_AP_FLAGS_PRIVACY; + else + g_assert_not_reached (); +} + +static guint32 +wpa_flags_for_idx (guint32 idx) +{ + if (idx == IDX_OPEN || idx == IDX_PRIV || idx == IDX_RSN_8021X + || idx == IDX_RSN_PSK_PCCMP_GCCMP || idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_NONE; + else if (idx == IDX_WPA_PSK_PTKIP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (IDX_WPA_RSN_PSK_PCCMP_GCCMP) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_8021X) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_KEY_MGMT_802_1X; + else + g_assert_not_reached (); +} + +static guint32 +rsn_flags_for_idx (guint32 idx) +{ + if (idx == IDX_OPEN || idx == IDX_PRIV || idx == IDX_WPA_8021X + || idx == IDX_WPA_PSK_PTKIP_GTKIP || idx == IDX_WPA_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_NONE; + else if (idx == IDX_RSN_PSK_PCCMP_GCCMP) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP) + return NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_WPA_RSN_PSK_PCCMP_GCCMP) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP | NM_802_11_AP_SEC_KEY_MGMT_PSK; + else if (idx == IDX_RSN_8021X) + return NM_802_11_AP_SEC_PAIR_CCMP | NM_802_11_AP_SEC_GROUP_CCMP | NM_802_11_AP_SEC_KEY_MGMT_802_1X; + else + g_assert_not_reached (); +} + +static guint32 +error_domain_for_idx (guint32 idx, guint num) +{ + if (idx == IDX_OPEN) + return NM_SETTING_WIRELESS_SECURITY_ERROR; + else if (idx == IDX_PRIV) { + if (num <= 3) + return NM_SETTING_802_1X_ERROR; + else + return NM_SETTING_WIRELESS_SECURITY_ERROR; + } else if (idx == IDX_WPA_PSK_PTKIP_GTKIP || idx == IDX_WPA_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_WPA_RSN_PSK_PCCMP_GCCMP || idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP || idx == IDX_RSN_PSK_PCCMP_GCCMP) + return NM_SETTING_WIRELESS_SECURITY_ERROR; + else + g_assert_not_reached (); +} + +static guint32 +error_code_for_idx (guint32 idx, guint num) +{ + if (idx == IDX_OPEN) + return NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY; + else if (idx == IDX_PRIV) { + if (num <= 3) + return NM_SETTING_802_1X_ERROR_MISSING_PROPERTY; + else + return NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY; + } else if (idx == IDX_WPA_PSK_PTKIP_GTKIP || idx == IDX_WPA_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_WPA_RSN_PSK_PCCMP_GCCMP || idx == IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP + || idx == IDX_RSN_PSK_PTKIP_PCCMP_GTKIP || idx == IDX_RSN_PSK_PCCMP_GCCMP) + return NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY; + else + g_assert_not_reached (); +} + +static void +test_ap_wpa_eap_connection_1 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + + test_ap_wpa_eap_connection_base (NULL, NULL, + flags_for_idx (idx), + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, + error_domain_for_idx (idx, 1), + error_code_for_idx (idx, 1)); +} + +static void +test_ap_wpa_eap_connection_2 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + + test_ap_wpa_eap_connection_base (NULL, NULL, + flags_for_idx (idx), + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + TRUE, + error_domain_for_idx (idx, 2), + error_code_for_idx (idx, 2)); +} + +static void +test_ap_wpa_eap_connection_3 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + + test_ap_wpa_eap_connection_base (NULL, "open", + flags_for_idx (idx), + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, + error_domain_for_idx (idx, 3), + error_code_for_idx (idx, 3)); +} + +static void +test_ap_wpa_eap_connection_4 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + + test_ap_wpa_eap_connection_base (NULL, "shared", + flags_for_idx (idx), + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, + error_domain_for_idx (idx, 4), + error_code_for_idx (idx, 4)); +} + +static void +test_ap_wpa_eap_connection_5 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + + test_ap_wpa_eap_connection_base ("wpa-eap", "open", + flags_for_idx (idx), + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, + error_domain_for_idx (idx, 5), + error_code_for_idx (idx, 5)); +} + +/*******************************************/ + +static void +test_priv_ap_empty_connection (void) +{ + NMConnection *src, *expected; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const char *ssid = "blahblah"; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that an empty connection is completed to a valid Static WEP + * connection when completed with an AP with the Privacy bit set. + */ + + src = nm_connection_new (); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + + /* Static WEP connection expected */ + expected = create_basic (ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec (expected, exp_wsec); + COMPARE (src, expected, success, error, 0, 0); + + g_object_unref (src); + g_object_unref (expected); +} + +/*******************************************/ + +static void +test_priv_ap_leap_connection_1 (gconstpointer add_wifi) +{ + NMConnection *src, *expected; + const char *ssid = "blahblah"; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const char *leap_username = "Bill Smith"; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0 }, + { NULL } }; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0 }, + { NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that an minimal LEAP connection specifying only key management and + * the LEAP username is completed to a full LEAP connection when completed + * with an AP with the Privacy bit set. + */ + + src = nm_connection_new (); + if (add_wifi) + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + /* We expect success here; since LEAP APs just set the 'privacy' flag + * there's no way to determine from the AP's beacon whether it's static WEP, + * dynamic WEP, or LEAP. + */ + expected = create_basic (ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec (expected, exp_wsec); + COMPARE (src, expected, success, error, 0, 0); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_priv_ap_leap_connection_2 (void) +{ + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that an minimal LEAP connection specifying only key management and + * the LEAP auth alg is completed to a full LEAP connection when completed + * with an AP with the Privacy bit set. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + success = complete_connection ("blahblah", bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + /* We expect failure here, we need a LEAP username */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_LEAP_REQUIRES_USERNAME); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_priv_ap_dynamic_wep_1 (void) +{ + NMConnection *src, *expected; + const char *ssid = "blahblah"; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + const KeyData both_8021x[] = { + { NM_SETTING_802_1X_EAP, "peap", 0 }, + { NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0 }, + { NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0 }, + { NULL } }; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that an minimal Dynamic WEP connection specifying key management, + * the auth algorithm, and valid 802.1x setting is completed to a valid + * Dynamic WEP connection when completed with an AP with the Privacy bit set. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + fill_8021x (src, both_8021x); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + + /* We expect a completed Dynamic WEP connection */ + expected = create_basic (ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec (expected, exp_wsec); + fill_8021x (expected, both_8021x); + COMPARE (src, expected, success, error, 0, 0); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_priv_ap_dynamic_wep_2 (void) +{ + NMConnection *src, *expected; + const char *ssid = "blahblah"; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + const KeyData both_8021x[] = { + { NM_SETTING_802_1X_EAP, "peap", 0 }, + { NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0 }, + { NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0 }, + { NULL } }; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that an minimal Dynamic WEP connection specifying only the auth + * algorithm and a valid 802.1x setting is completed to a valid Dynamic + * WEP connection when completed with an AP with the Privacy bit set. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + fill_8021x (src, both_8021x); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + + /* We expect a completed Dynamic WEP connection */ + expected = create_basic (ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec (expected, exp_wsec); + fill_8021x (expected, both_8021x); + COMPARE (src, expected, success, error, 0, 0); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_priv_ap_dynamic_wep_3 (void) +{ + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", 0 }, + { NULL } }; + const KeyData src_8021x[] = { + { NM_SETTING_802_1X_EAP, "peap", 0 }, + { NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0 }, + { NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Ensure that a basic connection specifying 'shared' auth and an 802.1x + * setting is rejected, as 802.1x is incompatible with 'shared' auth. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + fill_8021x (src, src_8021x); + success = complete_connection ("blahblah", bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, NM_802_11_AP_SEC_NONE, + FALSE, + src, &error); + /* Expect failure; shared is not compatible with dynamic WEP */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_priv_ap_wpa_psk_connection_1 (void) +{ + /* Test that a basic WPA-PSK connection is rejected when completion is + * attempted with an AP with just the Privacy bit set. Lack of WPA/RSN + * flags means the AP provides Static/Dynamic WEP or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base (NULL, NULL, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +static void +test_priv_ap_wpa_psk_connection_2 (void) +{ + /* Test that a basic WPA-PSK connection is rejected when completion is + * attempted with an AP with just the Privacy bit set. Lack of WPA/RSN + * flags means the AP provides Static/Dynamic WEP or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base (NULL, NULL, + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + TRUE, NULL); +} + +static void +test_priv_ap_wpa_psk_connection_3 (void) +{ + /* Test that a basic WPA-PSK connection specifying only the auth algorithm + * is rejected when completion is attempted with an AP with just the Privacy + * bit set. Lack of WPA/RSN flags means the AP provides Static/Dynamic WEP + * or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base (NULL, "open", + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +static void +test_priv_ap_wpa_psk_connection_4 (void) +{ + /* Test that a basic WPA-PSK connection specifying only the auth algorithm + * is rejected when completion is attempted with an AP with just the Privacy + * bit set. Lack of WPA/RSN flags means the AP provides Static/Dynamic WEP + * or LEAP, not WPA. Second, 'shared' auth is incompatible with WPA. + */ + test_ap_wpa_psk_connection_base (NULL, "shared", + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +static void +test_priv_ap_wpa_psk_connection_5 (void) +{ + /* Test that a WPA-PSK connection specifying both the key management and + * auth algorithm is rejected when completion is attempted with an AP with + * just the Privacy bit set. Lack of WPA/RSN flags means the AP provides + * Static/Dynamic WEP or LEAP, not WPA. + */ + test_ap_wpa_psk_connection_base ("wpa-psk", "open", + NM_802_11_AP_FLAGS_PRIVACY, + NM_802_11_AP_SEC_NONE, + NM_802_11_AP_SEC_NONE, + FALSE, NULL); +} + +/*******************************************/ + +static void +test_wpa_ap_empty_connection (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *src, *expected; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const char *ssid = "blahblah"; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that a basic WPA-PSK connection specifying just key management and + * the auth algorithm is completed successfully when given an AP with WPA + * or RSN flags. + */ + + src = nm_connection_new (); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, src, &error); + + /* WPA connection expected */ + expected = create_basic (ssid, NULL, NM_802_11_MODE_INFRA); + fill_wsec (expected, exp_wsec); + COMPARE (src, expected, success, error, 0, 0); + + g_object_unref (src); + g_object_unref (expected); +} + +/*******************************************/ + +static void +test_wpa_ap_leap_connection_1 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *src; + const char *ssid = "blahblah"; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const char *leap_username = "Bill Smith"; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that completion of a LEAP connection with a WPA-enabled AP is + * rejected since WPA APs (usually) do not support LEAP. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + success = complete_connection (ssid, bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, + src, &error); + /* Expect failure here; WPA APs don't support old-school LEAP */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_wpa_ap_leap_connection_2 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that completion of a LEAP connection with a WPA-enabled AP is + * rejected since WPA APs (usually) do not support LEAP. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + success = complete_connection ("blahblah", bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, + src, &error); + /* We expect failure here, we need a LEAP username */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_wpa_ap_dynamic_wep_connection (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *src; + const guint8 bssid[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + const KeyData src_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0 }, + { NULL } }; + gboolean success; + GError *error = NULL; + + /* Test that completion of a Dynamic WEP connection with a WPA-enabled AP is + * rejected since WPA APs (usually) do not support Dynamic WEP. + */ + + src = nm_connection_new (); + fill_wifi_empty (src); + fill_wsec (src, src_wsec); + success = complete_connection ("blahblah", bssid, + NM_802_11_MODE_INFRA, NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, + src, &error); + /* We expect failure here since Dynamic WEP is incompatible with WPA */ + COMPARE (src, NULL, success, error, NM_SETTING_WIRELESS_SECURITY_ERROR, NM_SETTING_WIRELESS_SECURITY_ERROR_INVALID_PROPERTY); + + g_object_unref (src); +} + +/*******************************************/ + +static void +test_wpa_ap_wpa_psk_connection_1 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *expected; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + + expected = nm_connection_new (); + fill_wsec (expected, exp_wsec); + test_ap_wpa_psk_connection_base (NULL, NULL, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, expected); + g_object_unref (expected); +} + +static void +test_wpa_ap_wpa_psk_connection_2 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *expected; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + + expected = nm_connection_new (); + fill_wsec (expected, exp_wsec); + test_ap_wpa_psk_connection_base (NULL, NULL, + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + TRUE, expected); + g_object_unref (expected); +} + +static void +test_wpa_ap_wpa_psk_connection_3 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *expected; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + + expected = nm_connection_new (); + fill_wsec (expected, exp_wsec); + test_ap_wpa_psk_connection_base (NULL, "open", + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, expected); + g_object_unref (expected); +} + +static void +test_wpa_ap_wpa_psk_connection_4 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + test_ap_wpa_psk_connection_base (NULL, "shared", + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, NULL); +} + +static void +test_wpa_ap_wpa_psk_connection_5 (gconstpointer data) +{ + guint idx = GPOINTER_TO_UINT (data); + NMConnection *expected; + const KeyData exp_wsec[] = { + { NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0 }, + { NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0 }, + { NULL } }; + + expected = nm_connection_new (); + fill_wsec (expected, exp_wsec); + test_ap_wpa_psk_connection_base ("wpa-psk", "open", + NM_802_11_AP_FLAGS_PRIVACY, + wpa_flags_for_idx (idx), + rsn_flags_for_idx (idx), + FALSE, expected); + g_object_unref (expected); +} + +/*******************************************/ + +static void +test_strength_dbm (void) +{ + /* boundary conditions first */ + g_assert_cmpint (nm_ap_utils_level_to_quality (-1), ==, 100); + g_assert_cmpint (nm_ap_utils_level_to_quality (-40), ==, 100); + g_assert_cmpint (nm_ap_utils_level_to_quality (-30), ==, 100); + g_assert_cmpint (nm_ap_utils_level_to_quality (-100), ==, 0); + g_assert_cmpint (nm_ap_utils_level_to_quality (-200), ==, 0); + + g_assert_cmpint (nm_ap_utils_level_to_quality (-81), ==, 32); + g_assert_cmpint (nm_ap_utils_level_to_quality (-92), ==, 14); + g_assert_cmpint (nm_ap_utils_level_to_quality (-74), ==, 44); + g_assert_cmpint (nm_ap_utils_level_to_quality (-81), ==, 32); + g_assert_cmpint (nm_ap_utils_level_to_quality (-66), ==, 57); +} + +static void +test_strength_percent (void) +{ + int i; + + /* boundary conditions first */ + g_assert_cmpint (nm_ap_utils_level_to_quality (0), ==, 0); + g_assert_cmpint (nm_ap_utils_level_to_quality (100), ==, 100); + g_assert_cmpint (nm_ap_utils_level_to_quality (110), ==, 100); + + for (i = 0; i <= 100; i++) + g_assert_cmpint (nm_ap_utils_level_to_quality (i), ==, i); +} + +static void +test_strength_wext (void) +{ + /* boundary conditions that we assume aren't WEXT first */ + g_assert_cmpint (nm_ap_utils_level_to_quality (256), ==, 100); + g_assert_cmpint (nm_ap_utils_level_to_quality (110), ==, 100); + + /* boundary conditions that we assume are WEXT */ + g_assert_cmpint (nm_ap_utils_level_to_quality (111), ==, 0); + g_assert_cmpint (nm_ap_utils_level_to_quality (150), ==, 0); + g_assert_cmpint (nm_ap_utils_level_to_quality (225), ==, 100); + g_assert_cmpint (nm_ap_utils_level_to_quality (255), ==, 100); + + g_assert_cmpint (nm_ap_utils_level_to_quality (157), ==, 2); + g_assert_cmpint (nm_ap_utils_level_to_quality (200), ==, 74); + g_assert_cmpint (nm_ap_utils_level_to_quality (215), ==, 99); +} + +/*******************************************/ + +int +main (int argc, char **argv) +{ + gsize i; + +#if !GLIB_CHECK_VERSION (2, 35, 0) + g_type_init (); +#endif + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/wifi/lock_bssid", + test_lock_bssid); + + /* Open AP tests; make sure that connections to be completed that have + * various security-related settings already set cause the completion + * to fail. + */ + g_test_add_func ("/wifi/open_ap/empty_connection", + test_open_ap_empty_connection); + g_test_add_data_func ("/wifi/open_ap/leap_connection/1", + (gconstpointer) TRUE, + test_open_ap_leap_connection_1); + g_test_add_data_func ("/wifi/open_ap/leap_connection/1_no_add_wifi", + (gconstpointer) FALSE, + test_open_ap_leap_connection_1); + g_test_add_func ("/wifi/open_ap/leap_connection/2", + test_open_ap_leap_connection_2); + g_test_add_data_func ("/wifi/open_ap/wep_connection", + (gconstpointer) TRUE, + test_open_ap_wep_connection); + g_test_add_data_func ("/wifi/open_ap/wep_connection", + (gconstpointer) FALSE, + test_open_ap_wep_connection); + + g_test_add_func ("/wifi/open_ap/wpa_psk_connection/1", + test_open_ap_wpa_psk_connection_1); + g_test_add_func ("/wifi/open_ap/wpa_psk_connection/2", + test_open_ap_wpa_psk_connection_2); + g_test_add_func ("/wifi/open_ap/wpa_psk_connection/3", + test_open_ap_wpa_psk_connection_3); + g_test_add_func ("/wifi/open_ap/wpa_psk_connection/4", + test_open_ap_wpa_psk_connection_4); + g_test_add_func ("/wifi/open_ap/wpa_psk_connection/5", + test_open_ap_wpa_psk_connection_5); + + g_test_add_data_func ("/wifi/open_ap/wpa_eap_connection/1", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_1); + g_test_add_data_func ("/wifi/open_ap/wpa_eap_connection/2", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_2); + g_test_add_data_func ("/wifi/open_ap/wpa_eap_connection/3", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_3); + g_test_add_data_func ("/wifi/open_ap/wpa_eap_connection/4", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_4); + g_test_add_data_func ("/wifi/open_ap/wpa_eap_connection/5", + (gconstpointer) IDX_OPEN, + test_ap_wpa_eap_connection_5); + + /* WEP AP tests */ + g_test_add_func ("/wifi/priv_ap/empty_connection", + test_priv_ap_empty_connection); + g_test_add_data_func ("/wifi/priv_ap/leap_connection/1", + (gconstpointer) FALSE, + test_priv_ap_leap_connection_1); + g_test_add_func ("/wifi/priv_ap/leap_connection/2", + test_priv_ap_leap_connection_2); + + g_test_add_func ("/wifi/priv_ap/dynamic_wep/1", + test_priv_ap_dynamic_wep_1); + g_test_add_func ("/wifi/priv_ap/dynamic_wep/2", + test_priv_ap_dynamic_wep_2); + g_test_add_func ("/wifi/priv_ap/dynamic_wep/3", + test_priv_ap_dynamic_wep_3); + + g_test_add_func ("/wifi/priv_ap/wpa_psk_connection/1", + test_priv_ap_wpa_psk_connection_1); + g_test_add_func ("/wifi/priv_ap/wpa_psk_connection/2", + test_priv_ap_wpa_psk_connection_2); + g_test_add_func ("/wifi/priv_ap/wpa_psk_connection/3", + test_priv_ap_wpa_psk_connection_3); + g_test_add_func ("/wifi/priv_ap/wpa_psk_connection/4", + test_priv_ap_wpa_psk_connection_4); + g_test_add_func ("/wifi/priv_ap/wpa_psk_connection/5", + test_priv_ap_wpa_psk_connection_5); + + g_test_add_data_func ("/wifi/priv_ap/wpa_eap_connection/1", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_1); + g_test_add_data_func ("/wifi/priv_ap/wpa_eap_connection/2", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_2); + g_test_add_data_func ("/wifi/priv_ap/wpa_eap_connection/3", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_3); + g_test_add_data_func ("/wifi/priv_ap/wpa_eap_connection/4", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_4); + g_test_add_data_func ("/wifi/priv_ap/wpa_eap_connection/5", + (gconstpointer) IDX_PRIV, + test_ap_wpa_eap_connection_5); + + /* WPA-PSK tests */ + for (i = IDX_WPA_PSK_PTKIP_GTKIP; i <= IDX_WPA_RSN_PSK_PCCMP_GCCMP; i++) { + g_test_add_data_func ("/wifi/wpa_psk/empty_connection", + (gconstpointer) i, + test_wpa_ap_empty_connection); + g_test_add_data_func ("/wifi/wpa_psk/leap_connection/1", + (gconstpointer) i, + test_wpa_ap_leap_connection_1); + g_test_add_data_func ("/wifi/wpa_psk/leap_connection/2", + (gconstpointer) i, + test_wpa_ap_leap_connection_2); + + g_test_add_data_func ("/wifi/wpa_psk/dynamic_wep_connection", + (gconstpointer) i, + test_wpa_ap_dynamic_wep_connection); + + g_test_add_data_func ("/wifi/wpa_psk/wpa_psk_connection/1", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_1); + g_test_add_data_func ("/wifi/wpa_psk/wpa_psk_connection/2", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_2); + g_test_add_data_func ("/wifi/wpa_psk/wpa_psk_connection/3", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_3); + g_test_add_data_func ("/wifi/wpa_psk/wpa_psk_connection/4", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_4); + g_test_add_data_func ("/wifi/wpa_psk/wpa_psk_connection/5", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_5); + + g_test_add_data_func ("/wifi/wpa_psk/wpa_eap_connection/1", + (gconstpointer) i, + test_ap_wpa_eap_connection_1); + g_test_add_data_func ("/wifi/wpa_psk/wpa_eap_connection/2", + (gconstpointer) i, + test_ap_wpa_eap_connection_2); + g_test_add_data_func ("/wifi/wpa_psk/wpa_eap_connection/3", + (gconstpointer) i, + test_ap_wpa_eap_connection_3); + g_test_add_data_func ("/wifi/wpa_psk/wpa_eap_connection/4", + (gconstpointer) i, + test_ap_wpa_eap_connection_4); + g_test_add_data_func ("/wifi/wpa_psk/wpa_eap_connection/5", + (gconstpointer) i, + test_ap_wpa_eap_connection_5); + } + + /* RSN-PSK tests */ + for (i = IDX_WPA_RSN_PSK_PTKIP_PCCMP_GTKIP; i <= IDX_RSN_PSK_PTKIP_PCCMP_GTKIP; i++) { + g_test_add_data_func ("/wifi/rsn_psk/empty_connection", + (gconstpointer) i, + test_wpa_ap_empty_connection); + g_test_add_data_func ("/wifi/rsn_psk/leap_connection/1", + (gconstpointer) i, + test_wpa_ap_leap_connection_1); + g_test_add_data_func ("/wifi/rsn_psk/leap_connection/2", + (gconstpointer) i, + test_wpa_ap_leap_connection_2); + + g_test_add_data_func ("/wifi/rsn_psk/dynamic_wep_connection", + (gconstpointer) i, + test_wpa_ap_dynamic_wep_connection); + + g_test_add_data_func ("/wifi/rsn_psk/wpa_psk_connection/1", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_1); + g_test_add_data_func ("/wifi/rsn_psk/wpa_psk_connection/2", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_2); + g_test_add_data_func ("/wifi/rsn_psk/wpa_psk_connection/3", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_3); + g_test_add_data_func ("/wifi/rsn_psk/wpa_psk_connection/4", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_4); + g_test_add_data_func ("/wifi/rsn_psk/wpa_psk_connection/5", + (gconstpointer) i, + test_wpa_ap_wpa_psk_connection_5); + + g_test_add_data_func ("/wifi/rsn_psk/wpa_eap_connection/1", + (gconstpointer) i, + test_ap_wpa_eap_connection_1); + g_test_add_data_func ("/wifi/rsn_psk/wpa_eap_connection/2", + (gconstpointer) i, + test_ap_wpa_eap_connection_2); + g_test_add_data_func ("/wifi/rsn_psk/wpa_eap_connection/3", + (gconstpointer) i, + test_ap_wpa_eap_connection_3); + g_test_add_data_func ("/wifi/rsn_psk/wpa_eap_connection/4", + (gconstpointer) i, + test_ap_wpa_eap_connection_4); + g_test_add_data_func ("/wifi/rsn_psk/wpa_eap_connection/5", + (gconstpointer) i, + test_ap_wpa_eap_connection_5); + } + + /* Scanned signal strength conversion tests */ + g_test_add_func ("/wifi/strength/dbm", + test_strength_dbm); + g_test_add_func ("/wifi/strength/percent", + test_strength_percent); + g_test_add_func ("/wifi/strength/wext", + test_strength_wext); + + return g_test_run (); +} diff --git a/src/devices/wimax/Makefile.am b/src/devices/wimax/Makefile.am new file mode 100644 index 000000000..def8bf62b --- /dev/null +++ b/src/devices/wimax/Makefile.am @@ -0,0 +1,63 @@ +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/platform \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-wimax"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(IWMX_SDK_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +pkglib_LTLIBRARIES = libnm-device-plugin-wimax.la + +SYMBOL_VIS_FILE=$(srcdir)/exports.ver + +libnm_device_plugin_wimax_la_SOURCES = \ + nm-wimax-factory.c \ + nm-device-wimax.c \ + nm-device-wimax.h \ + nm-wimax-nsp.c \ + nm-wimax-nsp.h \ + nm-wimax-types.h \ + nm-wimax-util.c \ + nm-wimax-util.h \ + iwmxsdk.c \ + iwmxsdk.h + +libnm_device_plugin_wimax_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_wimax_la_LIBADD = \ + $(DBUS_LIBS) \ + $(IWMX_SDK_LIBS) \ + $(GUDEV_LIBS) + +nm-wimax-nsp-glue.h: $(top_srcdir)/introspection/nm-wimax-nsp.xml + dbus-binding-tool --prefix=nm_wimax_nsp --mode=glib-server --output=$@ $< + +nm-device-wimax-glue.h: $(top_srcdir)/introspection/nm-device-wimax.xml + dbus-binding-tool --prefix=nm_device_wimax --mode=glib-server --output=$@ $< + +BUILT_SOURCES = \ + nm-wimax-nsp-glue.h \ + nm-device-wimax-glue.h + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) + +if ENABLE_TESTS + +check-local: + $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wimax.so $(SYMBOL_VIS_FILE) + +endif + diff --git a/src/devices/wimax/Makefile.in b/src/devices/wimax/Makefile.in new file mode 100644 index 000000000..ba16d2771 --- /dev/null +++ b/src/devices/wimax/Makefile.in @@ -0,0 +1,850 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/devices/wimax +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/build-aux/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \ + $(top_srcdir)/m4/compiler_warnings.m4 \ + $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gnome-code-coverage.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkglibdir)" +LTLIBRARIES = $(pkglib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libnm_device_plugin_wimax_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_libnm_device_plugin_wimax_la_OBJECTS = nm-wimax-factory.lo \ + nm-device-wimax.lo nm-wimax-nsp.lo nm-wimax-util.lo iwmxsdk.lo +libnm_device_plugin_wimax_la_OBJECTS = \ + $(am_libnm_device_plugin_wimax_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libnm_device_plugin_wimax_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libnm_device_plugin_wimax_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libnm_device_plugin_wimax_la_SOURCES) +DIST_SOURCES = $(libnm_device_plugin_wimax_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CKDB_PATH = @CKDB_PATH@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@ +DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SYS_DIR = @DBUS_SYS_DIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHCLIENT_PATH = @DHCLIENT_PATH@ +DHCPCD_PATH = @DHCPCD_PATH@ +DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@ +DLLTOOL = @DLLTOOL@ +DNSMASQ_PATH = @DNSMASQ_PATH@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MAKEFILE = @GLIB_MAKEFILE@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +IPTABLES_PATH = @IPTABLES_PATH@ +IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@ +IWMX_SDK_LIBS = @IWMX_SDK_LIBS@ +KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDL = @LIBDL@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBNDP_CFLAGS = @LIBNDP_CFLAGS@ +LIBNDP_LIBS = @LIBNDP_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@ +LIBSOUP_LIBS = @LIBSOUP_LIBS@ +LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@ +LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MM_GLIB_CFLAGS = @MM_GLIB_CFLAGS@ +MM_GLIB_LIBS = @MM_GLIB_LIBS@ +MOC = @MOC@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEWT_CFLAGS = @NEWT_CFLAGS@ +NEWT_LIBS = @NEWT_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NM_MAJOR_VERSION = @NM_MAJOR_VERSION@ +NM_MICRO_VERSION = @NM_MICRO_VERSION@ +NM_MINOR_VERSION = @NM_MINOR_VERSION@ +NM_MODIFY_SYSTEM_POLICY = @NM_MODIFY_SYSTEM_POLICY@ +NM_VERSION = @NM_VERSION@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POLKIT_CFLAGS = @POLKIT_CFLAGS@ +POLKIT_LIBS = @POLKIT_LIBS@ +POSUB = @POSUB@ +PPPD_PATH = @PPPD_PATH@ +PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@ +PPPOE_PATH = @PPPOE_PATH@ +QT_CFLAGS = @QT_CFLAGS@ +QT_LIBS = @QT_LIBS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@ +SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@ +SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@ +SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +SYSTEM_CA_PATH = @SYSTEM_CA_PATH@ +UDEV_BASE_DIR = @UDEV_BASE_DIR@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND_RULES = @VALGRIND_RULES@ +VAPIGEN = @VAPIGEN@ +VAPIGEN_MAKEFILE = @VAPIGEN_MAKEFILE@ +VAPIGEN_VAPIDIR = @VAPIGEN_VAPIDIR@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nmbinary = @nmbinary@ +nmconfdir = @nmconfdir@ +nmdatadir = @nmdatadir@ +nmrundir = @nmrundir@ +nmstatedir = @nmstatedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +with_dhclient = @with_dhclient@ +with_dhcpcd = @with_dhcpcd@ +with_netconfig = @with_netconfig@ +with_resolvconf = @with_resolvconf@ +with_valgrind = @with_valgrind@ +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/platform \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-wimax"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(IWMX_SDK_CFLAGS) \ + $(LIBNL_CFLAGS) \ + $(GUDEV_CFLAGS) + +pkglib_LTLIBRARIES = libnm-device-plugin-wimax.la +SYMBOL_VIS_FILE = $(srcdir)/exports.ver +libnm_device_plugin_wimax_la_SOURCES = \ + nm-wimax-factory.c \ + nm-device-wimax.c \ + nm-device-wimax.h \ + nm-wimax-nsp.c \ + nm-wimax-nsp.h \ + nm-wimax-types.h \ + nm-wimax-util.c \ + nm-wimax-util.h \ + iwmxsdk.c \ + iwmxsdk.h + +libnm_device_plugin_wimax_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_wimax_la_LIBADD = \ + $(DBUS_LIBS) \ + $(IWMX_SDK_LIBS) \ + $(GUDEV_LIBS) + +BUILT_SOURCES = \ + nm-wimax-nsp-glue.h \ + nm-device-wimax-glue.h + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/devices/wimax/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/devices/wimax/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ + } + +uninstall-pkglibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ + done + +clean-pkglibLTLIBRARIES: + -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) + @list='$(pkglib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libnm-device-plugin-wimax.la: $(libnm_device_plugin_wimax_la_OBJECTS) $(libnm_device_plugin_wimax_la_DEPENDENCIES) $(EXTRA_libnm_device_plugin_wimax_la_DEPENDENCIES) + $(AM_V_CCLD)$(libnm_device_plugin_wimax_la_LINK) -rpath $(pkglibdir) $(libnm_device_plugin_wimax_la_OBJECTS) $(libnm_device_plugin_wimax_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iwmxsdk.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-device-wimax.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wimax-factory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wimax-nsp.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wimax-util.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +@ENABLE_TESTS_FALSE@check-local: +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(pkglibdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-pkglibLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkglibLTLIBRARIES + +.MAKE: all check check-am install install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am check-local clean \ + clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkglibLTLIBRARIES install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkglibLTLIBRARIES + + +nm-wimax-nsp-glue.h: $(top_srcdir)/introspection/nm-wimax-nsp.xml + dbus-binding-tool --prefix=nm_wimax_nsp --mode=glib-server --output=$@ $< + +nm-device-wimax-glue.h: $(top_srcdir)/introspection/nm-device-wimax.xml + dbus-binding-tool --prefix=nm_device_wimax --mode=glib-server --output=$@ $< + +@ENABLE_TESTS_TRUE@check-local: +@ENABLE_TESTS_TRUE@ $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wimax.so $(SYMBOL_VIS_FILE) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/devices/wimax/exports.ver b/src/devices/wimax/exports.ver new file mode 100644 index 000000000..d2c451244 --- /dev/null +++ b/src/devices/wimax/exports.ver @@ -0,0 +1,7 @@ +{ +global: + nm_device_factory_create; + nm_device_factory_get_device_type; +local: + *; +}; diff --git a/src/devices/wimax/iwmxsdk.c b/src/devices/wimax/iwmxsdk.c new file mode 100644 index 000000000..d1ef7b683 --- /dev/null +++ b/src/devices/wimax/iwmxsdk.c @@ -0,0 +1,1517 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * + * Copyright (C) 2011 Red Hat, Inc. All rights reserved. + * Copyright (C) 2007-2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <linux/if.h> + +#include <glib.h> + +#include <WiMaxType.h> +#include <WiMaxAPI.h> +#include <WiMaxAPIEx.h> + +#include "logging/nm-logging.h" +#include "iwmxsdk.h" + +static WIMAX_API_DEVICE_ID g_api; +static GMutex add_remove_mutex; + +/* Misc utilities */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +/* Misc values */ +enum { + /* + * WARNING!!!!! + * + * ONLY ONE DEVICE SUPPORTED + * + * - on removal, there is no way to know which device was + * removed (the removed device is removed from the list and + * the callback doesn't have any more information than the + * index in the list that getlistdevice would return -- racy + * as hell). + * + * - on insertion, there is not enough information provided. + */ + IWMX_SDK_DEV_MAX = 1, +}; + +/* Yes, this is dirty; see above on IWMX_SDK_DEV_MAX */ +static struct wmxsdk *g_iwmx_sdk_devs[IWMX_SDK_DEV_MAX]; + +static struct wmxsdk *deviceid_to_wmxsdk(WIMAX_API_DEVICE_ID *device_id) +{ + unsigned cnt; + for (cnt = 0; cnt < IWMX_SDK_DEV_MAX; cnt++) { + struct wmxsdk *wmxsdk = g_iwmx_sdk_devs[cnt]; + if (wmxsdk && + wmxsdk->device_id.deviceIndex == device_id->deviceIndex) + return wmxsdk; + } + return NULL; +} + +static int deviceid_to_index(WIMAX_API_DEVICE_ID *device_id) +{ + unsigned cnt; + + for (cnt = 0; cnt < IWMX_SDK_DEV_MAX; cnt++) { + struct wmxsdk *wmxsdk = g_iwmx_sdk_devs[cnt]; + if (wmxsdk && wmxsdk->device_id.deviceIndex == device_id->deviceIndex) + return cnt; + } + return -1; +} + +struct wmxsdk *iwmx_sdk_get_wmxsdk_for_iface(const char *iface) +{ + unsigned cnt; + + for (cnt = 0; cnt < IWMX_SDK_DEV_MAX; cnt++) { + struct wmxsdk *wmxsdk = g_iwmx_sdk_devs[cnt]; + if (wmxsdk && !strcmp(wmxsdk->ifname, iface)) + return wmxsdk; + } + return NULL; +} + +/* + * FIXME: pulled it it out of some hole + * + * the cinr to percentage computation comes from the L3/L4 doc + * + * But some other places (L4 code) have a more complex, seemingly + * logarithmical computation. + * + * Oh well... + * + */ +static int cinr_to_percentage(int cinr) +{ + int strength; + if (cinr <= -5) + strength = 0; + else if (cinr >= 25) + strength = 100; + else /* Calc percentage on the value from -5 to 25 */ + strength = ((100UL * (cinr - -5)) / (25 - -5)); + return strength; +} + +/**************************************************************/ + +typedef struct { + WimaxNewWmxsdkFunc callback; + void *user_data; +} NewSdkCallback; + +static GMutex new_callbacks_mutex; +static GSList *new_callbacks = NULL; + +void iwmx_sdk_new_callback_register(WimaxNewWmxsdkFunc callback, void *user_data) +{ + NewSdkCallback *cb; + + cb = g_malloc0 (sizeof (NewSdkCallback)); + g_assert (cb); + cb->callback = callback; + cb->user_data = user_data; + + g_mutex_lock (&new_callbacks_mutex); + new_callbacks = g_slist_append (new_callbacks, cb); + g_mutex_unlock (&new_callbacks_mutex); +} + +void iwmx_sdk_new_callback_unregister(WimaxNewWmxsdkFunc callback, void *user_data) +{ + GSList *iter; + NewSdkCallback *found = NULL; + + g_mutex_lock (&new_callbacks_mutex); + for (iter = new_callbacks; iter; iter = g_slist_next (iter)) { + NewSdkCallback *cb = iter->data; + + if (cb->callback == callback && cb->user_data == user_data) { + found = cb; + break; + } + } + + if (found) { + new_callbacks = g_slist_remove (new_callbacks, found); + g_free (found); + } + g_mutex_unlock (&new_callbacks_mutex); +} + +static void iwmx_sdk_call_new_callbacks(struct wmxsdk *wmxsdk) +{ + GSList *iter; + + g_mutex_lock (&new_callbacks_mutex); + for (iter = new_callbacks; iter; iter = g_slist_next (iter)) { + NewSdkCallback *cb = iter->data; + + cb->callback (wmxsdk, cb->user_data); + } + g_mutex_unlock (&new_callbacks_mutex); +} + +/****************************************************************/ + +typedef struct { + struct wmxsdk *wmxsdk; + WIMAX_API_DEVICE_STATUS new_status; + WIMAX_API_DEVICE_STATUS old_status; + WIMAX_API_STATUS_REASON reason; + WIMAX_API_CONNECTION_PROGRESS_INFO progress; +} StateChangeInfo; + +static gboolean +state_change_handler(gpointer user_data) +{ + StateChangeInfo *info = user_data; + + if (info->wmxsdk->state_change_cb) { + info->wmxsdk->state_change_cb(info->wmxsdk, + info->new_status, + info->old_status, + info->reason, + info->progress, + info->wmxsdk->callback_data); + } + wmxsdk_unref(info->wmxsdk); + memset(info, 0, sizeof(*info)); + free(info); + return FALSE; +} + +static void +_schedule_state_change(struct wmxsdk *wmxsdk, + WIMAX_API_DEVICE_STATUS new_status, + WIMAX_API_DEVICE_STATUS old_status, + WIMAX_API_STATUS_REASON reason, + WIMAX_API_CONNECTION_PROGRESS_INFO progress) +{ + StateChangeInfo *info; + + info = malloc(sizeof (*info)); + if (!info) + return; + + memset(info, 0, sizeof(*info)); + info->wmxsdk = wmxsdk; + info->new_status = new_status; + info->old_status = old_status; + info->reason = reason; + info->progress = progress; + + wmxsdk_ref(wmxsdk); + g_idle_add(state_change_handler, info); +} + +typedef struct { + struct wmxsdk *wmxsdk; + WIMAX_API_MEDIA_STATUS media_status; +} MediaStatusInfo; + +static gboolean +media_status_change_handler(gpointer user_data) +{ + MediaStatusInfo *info = user_data; + + if (info->wmxsdk->media_status_cb) { + info->wmxsdk->media_status_cb(info->wmxsdk, + info->media_status, + info->wmxsdk->callback_data); + } + wmxsdk_unref(info->wmxsdk); + memset(info, 0, sizeof(*info)); + free(info); + return FALSE; +} + +static void +_schedule_media_status_change(struct wmxsdk *wmxsdk, + WIMAX_API_MEDIA_STATUS media_status) +{ + MediaStatusInfo *info; + + info = malloc(sizeof (*info)); + if (!info) + return; + + memset(info, 0, sizeof(*info)); + info->wmxsdk = wmxsdk; + info->media_status = media_status; + + wmxsdk_ref(wmxsdk); + g_idle_add(media_status_change_handler, info); +} + +typedef struct { + struct wmxsdk *wmxsdk; + WIMAX_API_NETWORK_CONNECTION_RESP result; +} ConnectResultInfo; + +static gboolean +connect_result_handler(gpointer user_data) +{ + ConnectResultInfo *info = user_data; + + if (info->wmxsdk->connect_result_cb) { + info->wmxsdk->connect_result_cb(info->wmxsdk, + info->result, + info->wmxsdk->callback_data); + } + wmxsdk_unref(info->wmxsdk); + memset(info, 0, sizeof(*info)); + free(info); + return FALSE; +} + +static void +_schedule_connect_result(struct wmxsdk *wmxsdk, + WIMAX_API_NETWORK_CONNECTION_RESP resp) +{ + ConnectResultInfo *info; + + info = malloc(sizeof (*info)); + if (!info) + return; + + memset(info, 0, sizeof(*info)); + info->wmxsdk = wmxsdk; + info->result = resp; + + wmxsdk_ref(wmxsdk); + g_idle_add(connect_result_handler, info); +} + +typedef struct { + struct wmxsdk *wmxsdk; + WIMAX_API_NSP_INFO_EX *nsps; + guint num_nsps; +} ScanResultInfo; + +static gboolean +scan_result_handler(gpointer user_data) +{ + ScanResultInfo *info = user_data; + + if (info->wmxsdk->scan_result_cb) { + info->wmxsdk->scan_result_cb(info->wmxsdk, + info->nsps, + info->num_nsps, + info->wmxsdk->callback_data); + } + wmxsdk_unref(info->wmxsdk); + free(info->nsps); + memset(info, 0, sizeof(*info)); + free(info); + return FALSE; +} + +static void +_schedule_scan_result(struct wmxsdk *wmxsdk, + WIMAX_API_NSP_INFO_EX *nsps, + guint num_nsps) +{ + ScanResultInfo *info; + size_t nsps_size; + int i, tmp; + + info = malloc(sizeof (*info)); + if (!info) + return; + + memset(info, 0, sizeof(*info)); + info->wmxsdk = wmxsdk; + + nsps_size = num_nsps * sizeof (WIMAX_API_NSP_INFO_EX); + info->nsps = malloc(nsps_size); + memcpy(info->nsps, nsps, nsps_size); + info->num_nsps = num_nsps; + + /* CAPI may report link quality as zero -- if it does check if it is a bug + * by computing it based on CINR. If it is different, use the computed one. + */ + for (i = 0; i < num_nsps; i++) { + WIMAX_API_NSP_INFO_EX *nsp = &info->nsps[i]; + + if (nsp->linkQuality == 0) { + tmp = cinr_to_percentage(nsp->CINR - 10); + if (tmp != nsp->linkQuality) + nsp->linkQuality = tmp; + } + } + + wmxsdk_ref(wmxsdk); + g_idle_add(scan_result_handler, info); +} + +static gboolean +removed_handler(gpointer user_data) +{ + struct wmxsdk *wmxsdk = user_data; + + if (wmxsdk->removed_cb) + wmxsdk->removed_cb(wmxsdk, wmxsdk->callback_data); + wmxsdk_unref(wmxsdk); + return FALSE; +} + +static void +_schedule_removed(struct wmxsdk *wmxsdk) +{ + wmxsdk_ref(wmxsdk); + g_idle_add(removed_handler, wmxsdk); +} + +/****************************************************************/ + +/* + * Convert a WiMAX API status to an string. + */ +const char *iwmx_sdk_dev_status_to_str(WIMAX_API_DEVICE_STATUS status) +{ + switch (status) { + case WIMAX_API_DEVICE_STATUS_UnInitialized: + return "uninitialized"; + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW: + return "rf off"; + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW: + return "rf off (hard-block)"; + case WIMAX_API_DEVICE_STATUS_RF_OFF_SW: + return "rf off (soft-block)"; + case WIMAX_API_DEVICE_STATUS_Ready: + return "ready"; + case WIMAX_API_DEVICE_STATUS_Scanning: + return "scanning"; + case WIMAX_API_DEVICE_STATUS_Connecting: + return "connecting"; + case WIMAX_API_DEVICE_STATUS_Data_Connected: + return "connected"; + default: + return "unknown"; + } +} + +const char *iwmx_sdk_reason_to_str(WIMAX_API_STATUS_REASON reason) +{ + switch (reason) { + case WIMAX_API_STATUS_REASON_Normal: + return "normal"; + + /**< Failed to complete NW entry with the selected operator (unspecified reason). */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_to_NW: + return "unspecified failure"; + + /**< Failed to complete ranging */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_Ranging: + return "ranging failed"; + + /**< SBC phase failed */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_SBC: + return "sbc failed"; + + /**< Security error. EAP authentication failed device level */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_EAP_AUTH_Device: + return "EAP device auth failed"; + + /**< Security error. EAP authentication failed user level */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_EAP_AUTH_user: + return "EAP user auth failed"; + + /**< Security error. Handshake failed */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_3_Way_Handshake: + return "3 way handshake failed"; + + /**< Registration failed */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_REG: + return "registration failed"; + + /**< Failed to initialize the data path (failed to perform DSA to one UL and one DL SFs). */ + case WIMAX_API_STATUS_REASON_Fail_to_connect_datapath: + return "datapath failed"; + + default: + return "unknown"; + } +} + +const char *iwmx_sdk_media_status_to_str(WIMAX_API_MEDIA_STATUS status) +{ + switch (status) { + case WIMAX_API_MEDIA_STATUS_LINK_UP: + return "link-up"; + case WIMAX_API_MEDIA_STATUS_LINK_DOWN: + return "link-down"; + case WIMAX_API_MEDIA_STATUS_LINK_RENEW: + return "link-renew"; + default: + return "unknown"; + } +} + +const char * +iwmx_sdk_con_progress_to_str(WIMAX_API_CONNECTION_PROGRESS_INFO progress) +{ + switch (progress) { + + /**< Device is in Ranging */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_Ranging: + return "ranging"; + + /**< Device is in SBC */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_SBC: + return "sbc"; + + /**< Device is in EAP authentication Device */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_EAP_authentication_Device: + return "eap-auth-device"; + + /**< Device is in EAP authentication User */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_EAP_authentication_User: + return "eap-auth-user"; + + /**< Device is in 3-way-handshake */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_3_way_handshake: + return "3way-handshake"; + + /**< Device is in Registration */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_Registration: + return "registration"; + + /**< Device is in De-registration */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_De_registration: + return "deregistration"; + + /**< Device is registered (operational) */ + case WIMAX_API_DEVICE_CONNECTION_PROGRESS_Registered: + return "registered"; + + default: + return "unknown"; + } +} + +/* + * Get the device's status from the device + * + * Does NOT cache the result + * Does NOT trigger a state change in NetworkManager + * + * Returns < 0 errno code on error, status code if ok. + */ +static WIMAX_API_DEVICE_STATUS iwmx_sdk_get_device_status(struct wmxsdk *wmxsdk) +{ + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + WIMAX_API_DEVICE_STATUS dev_status; + WIMAX_API_CONNECTION_PROGRESS_INFO pi; + + r = GetDeviceStatus(&wmxsdk->device_id, &dev_status, &pi); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot read device state: %d (%s)", r, errstr); + dev_status = -EIO; + } + return dev_status; +} + +/* + * Get the device's status from the device but return a string describing it + * + * Same conditions as iwmx_sdk_get_device_status(). + */ +static const char *iwmx_sdk_get_device_status_str(struct wmxsdk *wmxsdk) +{ + const char *result; + WIMAX_API_DEVICE_STATUS dev_status; + + dev_status = iwmx_sdk_get_device_status(wmxsdk); + if ((int) dev_status < 0) + result = "cannot read device state"; + else + result = iwmx_sdk_dev_status_to_str(dev_status); + return result; +} + +/* + * If the device is connected but we don't know about the network, + * create the knowledge of it. + * + * Asks the WiMAX API to report which NSP we are connected to and we + * create/update a network_el in the device's network list. Then + * return it. + * + * Returns NULL on error. + * + */ +WIMAX_API_CONNECTED_NSP_INFO_EX *iwmx_sdk_get_connected_network(struct wmxsdk *wmxsdk) +{ + WIMAX_API_CONNECTED_NSP_INFO_EX *nsp_info = NULL; + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + nsp_info = malloc(sizeof (*nsp_info)); + if (!nsp_info) { + nm_log_err(LOGD_WIMAX, "wmxsdk: cannot allocate NSP info"); + return NULL; + } + + r = GetConnectedNSPEx(&wmxsdk->device_id, nsp_info); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot get connected NSP info: %d (%s)", r, errstr); + free (nsp_info); + nsp_info = NULL; + } else { + /* Migth be 0 sometimes; fix that up */ + if (nsp_info->linkQuality == 0) { + int linkq_expected = cinr_to_percentage(nsp_info->CINR - 10); + if (linkq_expected != nsp_info->linkQuality) + nsp_info->linkQuality = linkq_expected; + } + } + + return nsp_info; +} + +/* + * Asks the WiMAX API to report current link statistics. + * + * Returns NULL on error. + * + */ +WIMAX_API_LINK_STATUS_INFO_EX *iwmx_sdk_get_link_status_info(struct wmxsdk *wmxsdk) +{ + WIMAX_API_LINK_STATUS_INFO_EX *stats = NULL; + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + /* Only report if connected */ + if (iwmxsdk_status_get(wmxsdk) < WIMAX_API_DEVICE_STATUS_Connecting) { + nm_log_err(LOGD_WIMAX, "wmxsdk: cannot get link status info unless connected"); + return NULL; + } + + stats = malloc(sizeof (*stats)); + if (!stats) { + nm_log_err(LOGD_WIMAX, "wmxsdk: cannot allocate links status info"); + return NULL; + } + + r = GetLinkStatusEx(&wmxsdk->device_id, stats); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot get link status info: %d (%s)", r, errstr); + free (stats); + stats = NULL; + } + + return stats; +} + +/* + * Callback for a RF State command + * + * Called by the WiMAX API when a command sent to change the RF state + * is completed. This is just a confirmation of what happened with the + * command. + * + * We don't do anything, as when the device changes state, the state + * change callback is called and that will fiddle with the NetworkManager + * internals. + */ +static void __iwmx_sdk_rf_state_cb(WIMAX_API_DEVICE_ID *device_id, + WIMAX_API_RF_STATE rf_state) +{ + nm_log_dbg(LOGD_WIMAX, "rf_state changed to %d", rf_state); +} + +/* + * Turn the radio on or off + * + * First it checks that we are in the right state before doing + * anything; there might be no need to do anything. + * + * Issue a command to the WiMAX API, wait for a callback confirming it + * is done. Sometimes the callback is missed -- in that case, do force + * a state change evaluation. + * + * Frustration note: + * + * Geezoos efing Xist, they make difficult even the most simple + * of the operations + * + * This thing is definitely a pain. If the radio is ON already + * and you switch it on again...well, there is no way to tell + * because you don't get a callback saying it basically + * suceeded. But on the other hand, if the thing was in a + * different state and action needs to be taken, you have to wait + * for a callback to confirm it's done. However, there is also an + * state change callback, which is almost the same, so now you + * have to handle things in two "unrelated" threads of execution. + * + * How the shpx are you expected to tell the difference? Check + * status first? On timeout? Nice gap (eighteen wheeler size) for + * race conditions. + */ +int iwmx_sdk_rf_state_set(struct wmxsdk *wmxsdk, WIMAX_API_RF_STATE rf_state) +{ + int result; + + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + WIMAX_API_DEVICE_STATUS dev_status; + + g_assert(rf_state == WIMAX_API_RF_ON || rf_state == WIMAX_API_RF_OFF); + + /* Guess what the current radio state is; if it is ON + * already, don't redo it. */ + dev_status = iwmx_sdk_get_device_status(wmxsdk); + if ((int) dev_status < 0) { + result = dev_status; + goto error_get_status; + } + switch (dev_status) { + case WIMAX_API_DEVICE_STATUS_UnInitialized: + result = -EINVAL; + goto error_cant_do; + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW: + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW: + nm_log_err(LOGD_WIMAX, "wmxsdk: cannot turn on radio: hw switch is off"); + result = -EPERM; + goto error_cant_do; + break; + case WIMAX_API_DEVICE_STATUS_RF_OFF_SW: + if (rf_state == WIMAX_API_RF_OFF) { + result = 0; + nm_log_dbg(LOGD_WIMAX, "radio is already off"); + goto out_done; + } + break; + case WIMAX_API_DEVICE_STATUS_Ready: + case WIMAX_API_DEVICE_STATUS_Scanning: + case WIMAX_API_DEVICE_STATUS_Connecting: + case WIMAX_API_DEVICE_STATUS_Data_Connected: + if (rf_state == WIMAX_API_RF_ON) { + result = 0; + nm_log_dbg(LOGD_WIMAX, "radio is already on"); + goto out_done; + } + break; + default: + g_assert(1); + } + /* Ok, flip the radio */ + r = CmdControlPowerManagement(&wmxsdk->device_id, rf_state); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot flip radio to %d: %d (%s) [device is in state %s]", + rf_state, r, errstr, iwmx_sdk_get_device_status_str(wmxsdk)); + result = -EIO; + } else + result = -EINPROGRESS; +out_done: +error_cant_do: +error_get_status: + return result; +} + +/* + * Read the cached device status + */ +WIMAX_API_DEVICE_STATUS iwmxsdk_status_get(struct wmxsdk *wmxsdk) +{ + WIMAX_API_DEVICE_STATUS status; + + g_mutex_lock(&wmxsdk->status_mutex); + status = wmxsdk->status; + g_mutex_unlock(&wmxsdk->status_mutex); + return status; +} + +/* + * Callback for a Connect command + * + * Called by the WiMAX API when a command sent to connect is + * completed. This is just a confirmation of what happened with the + * command. + * + * WE DON'T DO MUCH HERE -- the real meat happens when a state change + * callback is sent, where we detect we move to connected state (or + * from disconnecting to something else); the state change callback is + * called and that will fiddle with the NetworkManager internals. + */ +static void __iwmx_sdk_connect_cb(WIMAX_API_DEVICE_ID *device_id, + WIMAX_API_NETWORK_CONNECTION_RESP resp) +{ + WIMAX_API_DEVICE_STATUS status; + struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id); + + status = iwmxsdk_status_get(wmxsdk); + if (resp == WIMAX_API_CONNECTION_SUCCESS) { + if (status != WIMAX_API_DEVICE_STATUS_Data_Connected) { + nm_log_err(LOGD_WIMAX, "wmxsdk: error: connect worked, but state" + " didn't change (now it is %d [%s])", + status, + iwmx_sdk_dev_status_to_str(status)); + } + } else { + nm_log_err(LOGD_WIMAX, "wmxsdk: failed to connect (status %d: %s)", + status, iwmx_sdk_dev_status_to_str(status)); + } + + _schedule_connect_result(wmxsdk, resp); +} + +/* + * Connect to a network + * + * This function starts the connection process to a given network; + * when the device changes status, the status change callback will + * tell NetworkManager if the network is finally connected or not. + * + * One of the reasons it is done like that is to allow external tools + * to control the device and the plugin just passing the status so + * NetworkManager displays the right info. + */ +int iwmx_sdk_connect(struct wmxsdk *wmxsdk, const char *nsp_name) +{ + int result = 0; + + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + WIMAX_API_DEVICE_STATUS dev_status; + char sdk_name[MAX_SIZE_OF_NSP_NAME]; + + g_mutex_lock(&wmxsdk->connect_mutex); + /* Guess what the current radio state is; if it is ON + * already, don't redo it. */ + dev_status = iwmxsdk_status_get(wmxsdk); + if ((int) dev_status < 0) { + result = dev_status; + goto error_get_status; + } + switch (dev_status) { + case WIMAX_API_DEVICE_STATUS_UnInitialized: + nm_log_err(LOGD_WIMAX, "wmxsdk: SW BUG? HW is uninitialized"); + result = -EINVAL; + goto error_cant_do; + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW: + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW: + case WIMAX_API_DEVICE_STATUS_RF_OFF_SW: + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot connect: radio is off"); + result = -EPERM; + goto error_cant_do; + case WIMAX_API_DEVICE_STATUS_Ready: + case WIMAX_API_DEVICE_STATUS_Scanning: + break; + case WIMAX_API_DEVICE_STATUS_Connecting: + nm_log_dbg(LOGD_WIMAX, "Connect already pending, waiting for it"); + result = -EINPROGRESS; + goto error_cant_do; + case WIMAX_API_DEVICE_STATUS_Data_Connected: + nm_log_err(LOGD_WIMAX, "wmxsdk: BUG? need to disconnect?"); + result = -EINVAL; + goto error_cant_do; + default: + g_assert(1); + } + + /* The SDK treats the network name as wchar_t* while the contents are + * actually just UTF-8... WTF? Hand it a full buffer to work around + * boundary cases where the NSP name contains an odd # of characters. + */ + memset(sdk_name, 0, sizeof (sdk_name)); + memcpy(sdk_name, nsp_name, strlen (nsp_name)); + + /* Ok, do the connection, wait for a callback */ + r = CmdConnectToNetwork(&wmxsdk->device_id, &sdk_name[0], 0, 0); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot connect to network %s: %d (%s) - device is in state '%s'", + nsp_name, r, errstr, + iwmx_sdk_get_device_status_str(wmxsdk)); + result = -EIO; + } + +error_cant_do: +error_get_status: + g_mutex_unlock(&wmxsdk->connect_mutex); + return result; +} + +/* + * Callback for a Disconnect command + * + * Called by the WiMAX API when a command sent to connect is + * completed. This is just a confirmation of what happened with the + * command. + * + * When the device changes state, the state change callback is called + * and that will fiddle with the NetworkManager internals. + * + * We just update the result of the command and wake up anybody who is + * waiting for this conditional variable. + */ +static void __iwmx_sdk_disconnect_cb(WIMAX_API_DEVICE_ID *device_id, + WIMAX_API_NETWORK_CONNECTION_RESP resp) +{ + struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id); + WIMAX_API_DEVICE_STATUS status; + + status = iwmxsdk_status_get(wmxsdk); + if (resp == WIMAX_API_CONNECTION_SUCCESS) { + if (status == WIMAX_API_DEVICE_STATUS_Data_Connected) { + nm_log_err(LOGD_WIMAX, "wmxsdk: error: disconnect worked, " + "but state didn't change (now it is %d [%s])", status, + iwmx_sdk_dev_status_to_str(status)); + } + } else + nm_log_err(LOGD_WIMAX, "wmxsdk: failed to disconnect (status %d: %s)", + status, iwmx_sdk_dev_status_to_str(status)); +} + +/* + * Disconnect from a network + * + * This function tells the device to disconnect; the state change + * callback will take care of inform NetworkManager's internals. + */ +int iwmx_sdk_disconnect(struct wmxsdk *wmxsdk) +{ + int result; + + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + WIMAX_API_DEVICE_STATUS dev_status; + + g_mutex_lock(&wmxsdk->connect_mutex); + /* Guess what the current radio state is; if it is ON + * already, don't redo it. */ + dev_status = iwmx_sdk_get_device_status(wmxsdk); + if ((int) dev_status < 0) { + result = dev_status; + goto error_get_status; + } + switch (dev_status) { + case WIMAX_API_DEVICE_STATUS_UnInitialized: + nm_log_err(LOGD_WIMAX, "wmxsdk: SW BUG? HW is uninitialized"); + result = -EINVAL; + goto error_cant_do; + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW: + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW: + case WIMAX_API_DEVICE_STATUS_RF_OFF_SW: + nm_log_dbg(LOGD_WIMAX, "Cannot disconnect, radio is off; ignoring"); + result = 0; + goto error_cant_do; + case WIMAX_API_DEVICE_STATUS_Ready: + case WIMAX_API_DEVICE_STATUS_Scanning: + nm_log_dbg(LOGD_WIMAX, "Cannot disconnect, already disconnected; ignoring"); + result = 0; + goto error_cant_do; + case WIMAX_API_DEVICE_STATUS_Connecting: + case WIMAX_API_DEVICE_STATUS_Data_Connected: + break; + default: + g_assert(1); + } + /* Ok, flip the radio */ + r = CmdDisconnectFromNetwork(&wmxsdk->device_id); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot disconnect from network: %d (%s)", r, errstr); + result = -EIO; + } else + result = -EINPROGRESS; +error_cant_do: +error_get_status: + g_mutex_unlock(&wmxsdk->connect_mutex); + return result; +} + +/* + * Turn fast reconnect capability on/off + * + * This function tells wimaxd to turn fast reconnect on or off. + */ +int iwmx_sdk_set_fast_reconnect_enabled(struct wmxsdk *wmxsdk, int enabled) +{ + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + r = SetFastReconnectCapabilityStatus(&wmxsdk->device_id, !!enabled); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot set fast reconnect to %d: %d (%s)", + enabled, r, errstr); + return -EIO; + } + return 0; +} + +static void __iwmx_sdk_media_status_update_cb (WIMAX_API_DEVICE_ID_P device_id, + WIMAX_API_MEDIA_STATUS mediaStatus) +{ + struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id); + + /* Ignore redundant LINK_UP events */ + if ( mediaStatus == WIMAX_API_MEDIA_STATUS_LINK_UP + && wmxsdk->media_status == WIMAX_API_MEDIA_STATUS_LINK_UP) + return; + + wmxsdk->media_status = mediaStatus; + + nm_log_dbg(LOGD_WIMAX, "wmxsdk: media status change to (%d) %s", + mediaStatus, iwmx_sdk_media_status_to_str (mediaStatus)); + + _schedule_media_status_change(wmxsdk, mediaStatus); +} + +/* + * Callback for state change messages + * + * Just pass them to the state transition handler + */ +static void __iwmx_sdk_state_change_cb(WIMAX_API_DEVICE_ID *device_id, + WIMAX_API_DEVICE_STATUS status, + WIMAX_API_STATUS_REASON reason, + WIMAX_API_CONNECTION_PROGRESS_INFO pi) +{ + struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id); + WIMAX_API_DEVICE_STATUS old_status; + + nm_log_dbg(LOGD_WIMAX, "wmxsdk: state change to (%d) %s reason (%d) %s", + status, iwmx_sdk_dev_status_to_str (status), + reason, iwmx_sdk_reason_to_str (reason)); + + g_mutex_lock(&wmxsdk->status_mutex); + old_status = wmxsdk->status; + wmxsdk->status = status; + g_mutex_unlock(&wmxsdk->status_mutex); + + _schedule_state_change(wmxsdk, status, old_status, reason, pi); +} + +/* + * Called by _iwmx_sdk_*scan_cb() when [wide or preferred] scan results + * are available. + * + * From here we update NetworkManager's idea of which networks are available. + */ +static void __iwmx_sdk_scan_common_cb(WIMAX_API_DEVICE_ID *device_id, + WIMAX_API_NSP_INFO_EX *nsp_list, + UINT32 nsp_list_size) +{ + struct wmxsdk *wmxsdk = deviceid_to_wmxsdk(device_id); + + g_mutex_lock(&wmxsdk->network_mutex); + _schedule_scan_result(wmxsdk, nsp_list, nsp_list_size); + g_mutex_unlock(&wmxsdk->network_mutex); +} + +/* + * Called by the WiMAX API when we get a wide scan result + * + * We treat them same as wide, so we just call that. + */ +static void __iwmx_sdk_wide_scan_cb(WIMAX_API_DEVICE_ID *device_id, + WIMAX_API_NSP_INFO_EX *nsp_list, + UINT32 nsp_list_size) +{ + __iwmx_sdk_scan_common_cb(device_id, nsp_list, nsp_list_size); +} + +/* + * Called by the WiMAX API when we get a normal (non wide) scan result + * + * We treat them same as wide, so we just call that. + */ +static void __iwmx_sdk_scan_cb(WIMAX_API_DEVICE_ID *device_id, + WIMAX_API_NSP_INFO_EX *nsp_list, + UINT32 nsp_list_size, UINT32 searchProgress) +{ + __iwmx_sdk_scan_common_cb(device_id, nsp_list, nsp_list_size); +} + +/* + * Called to ask the device to scan for networks + * + * We don't really scan as the WiMAX SDK daemon scans in the + * background for us. We just get the results and hand them back via + * the scan_result_cb callback. + */ +int iwmx_sdk_get_networks(struct wmxsdk *wmxsdk) +{ + int result; + + UINT32 nsp_list_length = 10; + WIMAX_API_NSP_INFO_EX nsp_list[10]; /* FIXME: up to 32? */ + + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + r = GetNetworkListEx(&wmxsdk->device_id, nsp_list, &nsp_list_length); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot get network list: %d (%s)", r, errstr); + result = -EIO; + goto error_scan; + } + + if (nsp_list_length == 0) { + nm_log_dbg(LOGD_WIMAX, "no networks"); + } else + __iwmx_sdk_scan_common_cb(&wmxsdk->device_id, nsp_list, + nsp_list_length); + result = 0; +error_scan: + return result; +} + +/* + * Initialize the WiMAX API, register with it, setup callbacks + * + */ +static int iwmx_sdk_setup(struct wmxsdk *wmxsdk) +{ + int result, status; + + WIMAX_API_RET r; + + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + result = -ENFILE; + + /* device_id initialized by iwmx_sdk_dev_add */ + + r = WiMaxDeviceOpen(&wmxsdk->device_id); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot open device: %d (%s)", r, errstr); + goto error_wimaxdeviceopen; + } + + /* + * We scan in auto mode (in the background) + * + * Otherwise is messy -- if we have NetworkManager triggering a scan + * when we call iwmx_nm_scan() -> iwmx_sdk_scan(), most of the + * times that causes a race condition when the UI asks for a + * scan right before displaying the network menu. As there is + * no way to cancel an ongoing scan before connecting, we are + * stuck. So we do auto bg and have iwmx_sdk_scan() just return + * the current network list. + */ + r = SetConnectionMode(&wmxsdk->device_id, + WIMAX_API_CONNECTION_AUTO_SCAN_MANUAL_CONNECT); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot set connectin mode to manual: %d (%s)", r, errstr); + goto error_connection_mode; + } + + r = SubscribeControlPowerManagement(&wmxsdk->device_id, + __iwmx_sdk_rf_state_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to radio change events: %u (%s)", r, errstr); + result = -EIO; + goto error_subscribe_rf_state; + } + + r = SubscribeDeviceStatusChange(&wmxsdk->device_id, + __iwmx_sdk_state_change_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to state chaneg events: %d (%s)", r, errstr); + goto error_subscribe_state_change; + } + + r = SubscribeNetworkSearchWideScanEx(&wmxsdk->device_id, + __iwmx_sdk_wide_scan_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to wide scan events: %d (%s)", r, errstr); + goto error_subscribe_wide_scan; + } + r = SubscribeNetworkSearchEx(&wmxsdk->device_id, __iwmx_sdk_scan_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to scan events: %d (%s)", r, errstr); + goto error_subscribe_scan; + } + + r = SubscribeConnectToNetwork(&wmxsdk->device_id, + __iwmx_sdk_connect_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to connect events: %d (%s)", r, errstr); + goto error_subscribe_connect; + } + + r = SubscribeDisconnectToNetwork(&wmxsdk->device_id, + __iwmx_sdk_disconnect_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to disconnect events: %d (%s)", r, errstr); + goto error_subscribe_disconnect; + } + + r = SubscribeMediaStatusUpdate(&wmxsdk->device_id, __iwmx_sdk_media_status_update_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&wmxsdk->device_id, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot subscribe to media status events: %d (%s)", r, errstr); + goto error_subscribe_media_status; + } + + status = iwmx_sdk_get_device_status(wmxsdk); + if ((int) status < 0) + status = WIMAX_API_DEVICE_STATUS_UnInitialized; + + g_mutex_lock(&wmxsdk->status_mutex); + wmxsdk->status = status; + g_mutex_unlock(&wmxsdk->status_mutex); + + _schedule_state_change(wmxsdk, + status, + WIMAX_API_DEVICE_STATUS_UnInitialized, + WIMAX_API_STATUS_REASON_Normal, + WIMAX_API_DEVICE_CONNECTION_PROGRESS_Ranging); + + return 0; + + UnsubscribeMediaStatusUpdate(&wmxsdk->device_id); +error_subscribe_media_status: + UnsubscribeDisconnectToNetwork(&wmxsdk->device_id); +error_subscribe_disconnect: + UnsubscribeConnectToNetwork(&wmxsdk->device_id); +error_subscribe_connect: + UnsubscribeNetworkSearchEx(&wmxsdk->device_id); +error_subscribe_scan: + UnsubscribeNetworkSearchWideScanEx(&wmxsdk->device_id); +error_subscribe_wide_scan: + UnsubscribeDeviceStatusChange(&wmxsdk->device_id); +error_subscribe_state_change: + UnsubscribeControlPowerManagement(&wmxsdk->device_id); +error_subscribe_rf_state: +error_connection_mode: + WiMaxDeviceClose(&wmxsdk->device_id); +error_wimaxdeviceopen: + return result; +} + +/* + * Called when a device is torn down + * + * Cleanup all that is done in iwmx_sdk_setup(). Remove callbacks, + * unregister from the WiMAX API. + */ +static void iwmx_sdk_remove(struct wmxsdk *wmxsdk) +{ + UnsubscribeMediaStatusUpdate(&wmxsdk->device_id); + UnsubscribeDisconnectToNetwork(&wmxsdk->device_id); + UnsubscribeConnectToNetwork(&wmxsdk->device_id); + UnsubscribeNetworkSearchEx(&wmxsdk->device_id); + UnsubscribeNetworkSearchWideScanEx(&wmxsdk->device_id); + UnsubscribeDeviceStatusChange(&wmxsdk->device_id); + UnsubscribeControlPowerManagement(&wmxsdk->device_id); + WiMaxDeviceClose(&wmxsdk->device_id); +} + +void iwmx_sdk_set_callbacks(struct wmxsdk *wmxsdk, + WimaxStateChangeFunc state_change_cb, + WimaxMediaStatusFunc media_status_cb, + WimaxConnectResultFunc connect_result_cb, + WimaxScanResultFunc scan_result_cb, + WimaxRemovedFunc removed_cb, + void *user_data) +{ + wmxsdk->state_change_cb = state_change_cb; + wmxsdk->media_status_cb = media_status_cb; + wmxsdk->connect_result_cb = connect_result_cb; + wmxsdk->scan_result_cb = scan_result_cb; + wmxsdk->removed_cb = removed_cb; + wmxsdk->callback_data = user_data; +} + +/* Initialize a [zeroed] struct wmxsdk */ +static struct wmxsdk *wmxsdk_new(void) +{ + struct wmxsdk *wmxsdk; + + wmxsdk = malloc(sizeof(*wmxsdk)); + if (wmxsdk) { + memset(wmxsdk, 0, sizeof(*wmxsdk)); + + wmxsdk->refcount = 1; + g_mutex_init(&wmxsdk->network_mutex); + + wmxsdk->status = WIMAX_API_DEVICE_STATUS_UnInitialized; + g_mutex_init(&wmxsdk->status_mutex); + + g_mutex_init(&wmxsdk->connect_mutex); + } + return wmxsdk; +} + +struct wmxsdk *wmxsdk_ref(struct wmxsdk *wmxsdk) +{ + g_atomic_int_add(&wmxsdk->refcount, 1); + return wmxsdk; +} + +void wmxsdk_unref(struct wmxsdk *wmxsdk) +{ + if (g_atomic_int_dec_and_test(&wmxsdk->refcount)) { + g_mutex_clear(&wmxsdk->status_mutex); + g_mutex_clear(&wmxsdk->connect_mutex); + memset(wmxsdk, 0, sizeof(*wmxsdk)); + free(wmxsdk); + } +} + +static void iwmx_sdk_dev_add(unsigned idx, unsigned api_idx, const char *name) +{ + struct wmxsdk *wmxsdk; + const char *s; + + if (idx >= IWMX_SDK_DEV_MAX) { + nm_log_err(LOGD_WIMAX, "BUG! idx (%u) >= IWMX_SDK_DEV_MAX (%u)", idx, IWMX_SDK_DEV_MAX); + return; + } + if (g_iwmx_sdk_devs[idx] != NULL) { + nm_log_err(LOGD_WIMAX, "BUG! device index %u already enumerated?", idx); + return; + } + + wmxsdk = wmxsdk_new(); + if (wmxsdk == NULL) { + nm_log_err(LOGD_WIMAX, "Can't allocate %zu bytes", sizeof(*wmxsdk)); + return; + } + + /* + * This depends on a hack in the WiMAX Network Service; it has + * to return, as part of the device name, a string "if:IFNAME" + * where the OS's device name is stored. + */ + s = strstr(name, "if:"); + if (s == NULL + || sscanf(s, "if:%15[^ \f\n\r\t\v]", wmxsdk->ifname) != 1) { + nm_log_err(LOGD_WIMAX, "Cannot extract network interface name off '%s'", + name); + goto error; + } + nm_log_dbg(LOGD_WIMAX, "network interface name: '%s'", wmxsdk->ifname); + + strncpy(wmxsdk->name, name, sizeof(wmxsdk->name)); + wmxsdk->device_id.privilege = WIMAX_API_PRIVILEGE_READ_WRITE; + wmxsdk->device_id.deviceIndex = api_idx; + + if (iwmx_sdk_setup(wmxsdk) != 0) { + nm_log_err(LOGD_WIMAX, "wxmsdk: %s: cannot set up interface", wmxsdk->ifname); + goto error; + } + + g_iwmx_sdk_devs[idx] = wmxsdk; + + /* Notify listeners of new devices */ + iwmx_sdk_call_new_callbacks (wmxsdk); + return; + +error: + wmxsdk_unref(wmxsdk); + return; +} + +static void iwmx_sdk_dev_rm(unsigned idx) +{ + struct wmxsdk *wmxsdk; + + if (idx >= IWMX_SDK_DEV_MAX) { + nm_log_err(LOGD_WIMAX, "BUG! idx (%u) >= IWMX_SDK_DEV_MAX (%u)", idx, IWMX_SDK_DEV_MAX); + return; + } + + wmxsdk = g_iwmx_sdk_devs[idx]; + _schedule_removed(wmxsdk); + iwmx_sdk_remove(wmxsdk); + wmxsdk_unref(wmxsdk); + g_iwmx_sdk_devs[idx] = NULL; +} + +static void iwmx_sdk_addremove_cb(WIMAX_API_DEVICE_ID *devid, + BOOL presence) +{ + unsigned int cnt; + WIMAX_API_RET r; + WIMAX_API_HW_DEVICE_ID device_id_list[5]; + UINT32 device_id_list_size = ARRAY_SIZE(device_id_list); + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + g_mutex_lock(&add_remove_mutex); + + nm_log_dbg(LOGD_WIMAX, "cb: handle %u index #%u is %d", devid->sdkHandle, + devid->deviceIndex, presence); + + r = GetListDevice(devid, device_id_list, &device_id_list_size); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(devid, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot obtain list of devices: %d (%s)", r, errstr); + goto out; + } + + if (device_id_list_size == 0) { + nm_log_dbg(LOGD_WIMAX, "No WiMAX devices reported"); + } else { + for (cnt = 0; cnt < device_id_list_size; cnt++) { + WIMAX_API_HW_DEVICE_ID *dev = + device_id_list + cnt; + nm_log_dbg(LOGD_WIMAX, "#%u index #%u device %s", cnt, + dev->deviceIndex, dev->deviceName); + } + } + + if (presence) { + WIMAX_API_HW_DEVICE_ID *dev; + + /* Make sure the wimax NS isn't lying to us */ + if (device_id_list_size < devid->deviceIndex) { + nm_log_err(LOGD_WIMAX, "wmxsdk: changed device (%u) not in the list? (%u items)", + devid->deviceIndex, device_id_list_size); + goto out; + } + + /* Add the device to our internal list */ + dev = device_id_list + devid->deviceIndex; + iwmx_sdk_dev_add(devid->deviceIndex, dev->deviceIndex, dev->deviceName); + } else { + /* Remove the device from our internal list */ + int idx = deviceid_to_index(devid); + + if (idx >= 0) + iwmx_sdk_dev_rm(idx); + } + +out: + g_mutex_unlock(&add_remove_mutex); +} + +/* + * Initialize the WiMAX API, register with it, setup callbacks for + * device coming up / dissapearing + */ +int iwmx_sdk_api_init(void) +{ + int result; + unsigned int cnt; + WIMAX_API_RET r; + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + WIMAX_API_HW_DEVICE_ID device_id_list[5]; + UINT32 device_id_list_size = ARRAY_SIZE(device_id_list); + + memset(&g_api, 0, sizeof(g_api)); + g_api.privilege = WIMAX_API_PRIVILEGE_READ_WRITE; + + result = -EIO; + r = WiMaxAPIOpen(&g_api); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&g_api, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: WiMaxAPIOpen failed with %d (%s)", r, errstr); + goto error_wimaxapiopen; + } + + r = SubscribeDeviceInsertRemove(&g_api, iwmx_sdk_addremove_cb); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&g_api, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: insert/remove subscribe failed with %d (%s)", r, errstr); + goto error_close; + } + + r = GetListDevice(&g_api, device_id_list, &device_id_list_size); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&g_api, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: Cannot obtain list of devices: %d (%s)", r, errstr); + goto error_close; + } + if (device_id_list_size < g_api.deviceIndex) { + nm_log_err(LOGD_WIMAX, "wmxsdk: changed device (%u) not in the list? (%u items)", + g_api.deviceIndex, device_id_list_size); + } + + if (device_id_list_size == 0) { + nm_log_dbg(LOGD_WIMAX, "No WiMAX devices reported"); + } else { + for (cnt = 0; cnt < device_id_list_size; cnt++) { + WIMAX_API_HW_DEVICE_ID *dev = device_id_list + cnt; + nm_log_dbg(LOGD_WIMAX, "#%u index #%u device %s", cnt, dev->deviceIndex, dev->deviceName); + iwmx_sdk_dev_add(cnt, dev->deviceIndex, dev->deviceName); + } + } + return 0; + +error_close: + WiMaxAPIClose(&g_api); +error_wimaxapiopen: + return result; +} + +void iwmx_sdk_api_exit(void) +{ + WIMAX_API_RET r; + + char errstr[512]; + UINT32 errstr_size = sizeof(errstr); + + r = WiMaxAPIClose(&g_api); + if (r != WIMAX_API_RET_SUCCESS) { + GetErrorString(&g_api, r, errstr, &errstr_size); + nm_log_err(LOGD_WIMAX, "wmxsdk: WiMaxAPIClose failed with %d (%s)", r, errstr); + } + return; +} diff --git a/src/devices/wimax/iwmxsdk.h b/src/devices/wimax/iwmxsdk.h new file mode 100644 index 000000000..785ca8c2f --- /dev/null +++ b/src/devices/wimax/iwmxsdk.h @@ -0,0 +1,111 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * + * Copyright (C) 2011 Red Hat, Inc. All rights reserved. + * Copyright (C) 2007-2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef IWMXSDK_H +#define IWMXSDK_H + +#include <wimax/WiMaxType.h> +#include <wimax/WiMaxTypesEx.h> +#include <wimax/WiMaxAPIEx.h> + +struct wmxsdk; + +typedef void (*WimaxNewWmxsdkFunc) (struct wmxsdk *wmxsdk, void *user_data); + +typedef void (*WimaxStateChangeFunc) (struct wmxsdk *wmxsdk, + WIMAX_API_DEVICE_STATUS new_status, + WIMAX_API_DEVICE_STATUS old_status, + WIMAX_API_STATUS_REASON reason, + WIMAX_API_CONNECTION_PROGRESS_INFO info, + void *user_data); + +typedef void (*WimaxMediaStatusFunc) (struct wmxsdk *wmxsdk, + WIMAX_API_MEDIA_STATUS media_status, + void *user_data); + +typedef void (*WimaxConnectResultFunc) (struct wmxsdk *wmxsdk, + WIMAX_API_NETWORK_CONNECTION_RESP resp, + void *user_data); + +typedef void (*WimaxScanResultFunc) (struct wmxsdk *wmxsdk, + WIMAX_API_NSP_INFO_EX *nsps, + guint num_nsps, + void *user_data); + +typedef void (*WimaxRemovedFunc) (struct wmxsdk *wmxsdk, void *user_data); + +struct wmxsdk { + gint refcount; + + WIMAX_API_DEVICE_ID device_id; + + WimaxStateChangeFunc state_change_cb; + WimaxMediaStatusFunc media_status_cb; + WimaxConnectResultFunc connect_result_cb; + WimaxScanResultFunc scan_result_cb; + WimaxRemovedFunc removed_cb; + void *callback_data; + + GMutex network_mutex; + + WIMAX_API_DEVICE_STATUS status; + WIMAX_API_MEDIA_STATUS media_status; + GMutex status_mutex; + + GMutex connect_mutex; + + char name[100]; + char ifname[16]; +}; + +struct wmxsdk *iwmx_sdk_get_wmxsdk_for_iface(const char *iface); + +struct wmxsdk *wmxsdk_ref(struct wmxsdk *wmxsdk); +void wmxsdk_unref(struct wmxsdk *wmxsdk); + +/* Register/unregister callbacks when a new wmxsdk is set up */ +void iwmx_sdk_new_callback_register(WimaxNewWmxsdkFunc callback, void *user_data); +void iwmx_sdk_new_callback_unregister(WimaxNewWmxsdkFunc callback, void *user_data); + +void iwmx_sdk_set_callbacks(struct wmxsdk *wmxsdk, + WimaxStateChangeFunc state_change_cb, + WimaxMediaStatusFunc media_status_func, + WimaxConnectResultFunc connect_result_cb, + WimaxScanResultFunc scan_result_cb, + WimaxRemovedFunc removed_cb, + void *user_data); + +WIMAX_API_DEVICE_STATUS iwmxsdk_status_get(struct wmxsdk *wmxsdk); +int iwmx_sdk_connect(struct wmxsdk *wmxsdk, const char *nsp_name); +int iwmx_sdk_disconnect(struct wmxsdk *wmxsdk); +int iwmx_sdk_set_fast_reconnect_enabled(struct wmxsdk *wmxsdk, int enabled); +WIMAX_API_CONNECTED_NSP_INFO_EX *iwmx_sdk_get_connected_network(struct wmxsdk *wmxsdk); +WIMAX_API_LINK_STATUS_INFO_EX *iwmx_sdk_get_link_status_info(struct wmxsdk *wmxsdk); +const char *iwmx_sdk_dev_status_to_str(WIMAX_API_DEVICE_STATUS status); +const char *iwmx_sdk_reason_to_str(WIMAX_API_STATUS_REASON reason); +const char *iwmx_sdk_media_status_to_str(WIMAX_API_MEDIA_STATUS status); +const char *iwmx_sdk_con_progress_to_str(WIMAX_API_CONNECTION_PROGRESS_INFO progress); +int iwmx_sdk_rf_state_set(struct wmxsdk *wmxsdk, WIMAX_API_RF_STATE rf_state); +int iwmx_sdk_get_networks(struct wmxsdk *wmxsdk); +int iwmx_sdk_api_init(void); +void iwmx_sdk_api_exit(void); + +#endif /* IWMXSDK_H */ diff --git a/src/devices/wimax/nm-device-wimax.c b/src/devices/wimax/nm-device-wimax.c new file mode 100644 index 000000000..1bdda4899 --- /dev/null +++ b/src/devices/wimax/nm-device-wimax.c @@ -0,0 +1,1445 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2010 - 2011 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <net/ethernet.h> +#include <sys/socket.h> +#include <linux/if.h> +#include <netinet/ether.h> + +#include <WiMaxAPI.h> +#include <WiMaxAPIEx.h> + +#include "nm-device-wimax.h" +#include "nm-wimax-util.h" +#include "nm-logging.h" +#include "nm-device-private.h" +#include "NetworkManagerUtils.h" +#include "nm-dbus-manager.h" +#include "nm-connection.h" +#include "nm-setting-connection.h" +#include "nm-setting-wimax.h" +#include "nm-utils.h" +#include "nm-rfkill-manager.h" +#include "iwmxsdk.h" +#include "nm-enum-types.h" +#include "nm-dbus-glib-types.h" + +static gboolean impl_device_get_nsp_list (NMDeviceWimax *device, GPtrArray **list, GError **error); + +#include "nm-device-wimax-glue.h" + +G_DEFINE_TYPE (NMDeviceWimax, nm_device_wimax, NM_TYPE_DEVICE) + +enum { + PROP_0, + PROP_NSPS, + PROP_ACTIVE_NSP, + PROP_CENTER_FREQ, + PROP_RSSI, + PROP_CINR, + PROP_TX_POWER, + PROP_BSID, + + LAST_PROP +}; + +enum { + NSP_ADDED, + NSP_REMOVED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +#define NM_DEVICE_WIMAX_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + NM_TYPE_DEVICE_WIMAX, \ + NMDeviceWimaxPrivate)) + +typedef struct { + gboolean disposed; + + struct wmxsdk *sdk; + WIMAX_API_DEVICE_STATUS status; + gboolean connect_failed; + + gboolean enabled; + gboolean wimaxd_enabled; + guint activation_timeout_id; + + /* Track whether stage1 (Prepare) is completed yet or not */ + gboolean prepare_done; + + guint sdk_action_defer_id; + + guint link_timeout_id; + guint poll_id; + + GSList *nsp_list; + NMWimaxNsp *current_nsp; + + /* interesting stuff when connected */ + guint center_freq; + gint rssi; + gint cinr; + gint tx_power; + char *bsid; +} NMDeviceWimaxPrivate; + +/***********************************************************/ + +#define NM_WIMAX_ERROR (nm_wimax_error_quark ()) + +static GQuark +nm_wimax_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-wimax-error"); + return quark; +} + +/***********************************************************/ + +static gboolean +impl_device_get_nsp_list (NMDeviceWimax *self, GPtrArray **nsps, GError **error) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + GSList *iter; + + *nsps = g_ptr_array_sized_new (4); + for (iter = priv->nsp_list; iter; iter = iter->next) + g_ptr_array_add (*nsps, g_strdup (nm_wimax_nsp_get_dbus_path (NM_WIMAX_NSP (iter->data)))); + + return TRUE; +} + +static void +set_current_nsp (NMDeviceWimax *self, NMWimaxNsp *new_nsp) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + NMWimaxNsp *old_nsp; + gboolean path_changed = FALSE; + + old_nsp = priv->current_nsp; + priv->current_nsp = NULL; + + if (new_nsp) + priv->current_nsp = g_object_ref (new_nsp); + + if (old_nsp && new_nsp) { + path_changed = (g_strcmp0 (nm_wimax_nsp_get_dbus_path (old_nsp), + nm_wimax_nsp_get_dbus_path (new_nsp)) != 0); + } + + /* Only notify if it's really changed */ + if (old_nsp != new_nsp || path_changed) + g_object_notify (G_OBJECT (self), NM_DEVICE_WIMAX_ACTIVE_NSP); + + if (old_nsp) + g_object_unref (old_nsp); +} + +static gboolean +activation_timed_out (gpointer data) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (data); + + priv->activation_timeout_id = 0; + nm_device_state_changed (NM_DEVICE (data), NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + + return FALSE; +} + +static void +emit_nsp_added_removed (NMDeviceWimax *self, + guint signum, + NMWimaxNsp *nsp, + gboolean recheck_available_connections) +{ + g_signal_emit (self, signals[signum], 0, nsp); + g_object_notify (G_OBJECT (self), NM_DEVICE_WIMAX_NSPS); + nm_device_emit_recheck_auto_activate (NM_DEVICE (self)); + if (recheck_available_connections) + nm_device_recheck_available_connections (NM_DEVICE (self)); +} + +static void +remove_all_nsps (NMDeviceWimax *self) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + set_current_nsp (self, NULL); + + while (priv->nsp_list) { + NMWimaxNsp *nsp = NM_WIMAX_NSP (priv->nsp_list->data); + + priv->nsp_list = g_slist_remove (priv->nsp_list, nsp); + emit_nsp_added_removed (self, NSP_REMOVED, nsp, FALSE); + g_object_unref (nsp); + } + + nm_device_recheck_available_connections (NM_DEVICE (self)); +} + +static NMWimaxNsp * +get_nsp_by_name (NMDeviceWimax *self, const char *name) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + GSList *iter; + + g_return_val_if_fail (name, NULL); + + for (iter = priv->nsp_list; iter; iter = iter->next) { + NMWimaxNsp *nsp = NM_WIMAX_NSP (iter->data); + + if (!g_strcmp0 (nm_wimax_nsp_get_name (nsp), name)) + return nsp; + } + + return NULL; +} + +static NMWimaxNsp * +get_nsp_by_path (NMDeviceWimax *self, const char *path) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + GSList *iter; + + g_return_val_if_fail (path, NULL); + + for (iter = priv->nsp_list; iter; iter = iter->next) { + NMWimaxNsp *nsp = NM_WIMAX_NSP (iter->data); + + if (!strcmp (nm_wimax_nsp_get_dbus_path (nsp), path)) + return nsp; + } + + return NULL; +} + +static gboolean +update_availability (NMDeviceWimax *self, gboolean old_available) +{ + NMDevice *device = NM_DEVICE (self); + NMDeviceState state; + gboolean new_available, changed = FALSE; + + new_available = nm_device_is_available (device); + if (new_available == old_available) + return FALSE; + + state = nm_device_get_state (device); + if (state == NM_DEVICE_STATE_UNAVAILABLE) { + if (new_available == TRUE) { + nm_device_state_changed (device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_NONE); + changed = TRUE; + } + } else if (state >= NM_DEVICE_STATE_DISCONNECTED) { + if (new_available == FALSE) { + nm_device_state_changed (device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + changed = TRUE; + } + } + + return changed; +} + +/* NMDeviceInterface interface */ + +static void +set_enabled (NMDevice *device, gboolean enabled) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (device); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + gboolean old_available; + int ret; + const char *iface; + + iface = nm_device_get_iface (NM_DEVICE (self)); + + nm_log_dbg (LOGD_WIMAX, "(%s): setting radio enabled %d -> %d", + iface, priv->enabled, enabled); + if (priv->enabled == enabled) + return; + + old_available = nm_device_is_available (NM_DEVICE (device)); + priv->enabled = enabled; + + nm_log_dbg (LOGD_WIMAX, "(%s): radio now %s", + iface, priv->enabled ? "enabled" : "disabled"); + + /* Set the WiMAX device RF state to the current user-specified enabled state */ + if (priv->sdk) { + ret = iwmx_sdk_rf_state_set (priv->sdk, + enabled ? WIMAX_API_RF_ON : WIMAX_API_RF_OFF); + if (ret < 0 && ret != -EINPROGRESS) { + nm_log_warn (LOGD_WIMAX, "(%s): failed to %s radio", + iface, priv->enabled ? "enable" : "disable"); + } + } + + update_availability (self, old_available); +} + +/* NMDevice methods */ + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMSettingConnection *s_con; + NMSettingWimax *s_wimax; + const char *connection_type; + const GByteArray *mac; + + if (!NM_DEVICE_CLASS (nm_device_wimax_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + connection_type = nm_setting_connection_get_connection_type (s_con); + if (strcmp (connection_type, NM_SETTING_WIMAX_SETTING_NAME)) + return FALSE; + + s_wimax = nm_connection_get_setting_wimax (connection); + if (!s_wimax) + return FALSE; + + mac = nm_setting_wimax_get_mac_address (s_wimax); + if (mac && memcmp (mac->data, nm_device_get_hw_address (device, NULL), ETH_ALEN)) + return FALSE; + + return TRUE; +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device); + const GSList *ns_iter = NULL; + NMWimaxNsp *nsp; + + if (specific_object) { + nsp = get_nsp_by_path (NM_DEVICE_WIMAX (device), specific_object); + return nsp ? nm_wimax_nsp_check_compatible (nsp, connection) : FALSE; + } + + /* Ensure the connection applies to an NSP in the scan list */ + for (ns_iter = priv->nsp_list; ns_iter; ns_iter = ns_iter->next) { + if (nm_wimax_nsp_check_compatible (NM_WIMAX_NSP (ns_iter->data), connection)) + return TRUE; + } + + return FALSE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (device); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + NMSettingWimax *s_wimax; + const GByteArray *setting_mac; + const guint8 *hw_address; + char *format; + const char *nsp_name = NULL; + NMWimaxNsp *nsp = NULL; + GSList *iter; + + s_wimax = nm_connection_get_setting_wimax (connection); + + if (!specific_object) { + /* If not given a specific object, we need at minimum an NSP name */ + if (!s_wimax) { + g_set_error_literal (error, + NM_WIMAX_ERROR, + NM_WIMAX_ERROR_CONNECTION_INVALID, + "A 'wimax' setting is required if no NSP path was given."); + return FALSE; + } + + nsp_name = nm_setting_wimax_get_network_name (s_wimax); + if (!nsp_name || !strlen (nsp_name)) { + g_set_error_literal (error, + NM_WIMAX_ERROR, + NM_WIMAX_ERROR_CONNECTION_INVALID, + "A 'wimax' setting with a valid network name is required if no NSP path was given."); + return FALSE; + } + + /* Find a compatible NSP in the list */ + nsp = get_nsp_by_name (self, nsp_name); + + /* If we still don't have an NSP, then the WiMAX settings needs to be + * fully specified by the client. Might not be able to find the NSP + * if the scan didn't find the NSP yet. + */ + if (!nsp) { + if (!nm_setting_verify (NM_SETTING (s_wimax), NULL, error)) + return FALSE; + } + } else { + /* Find a compatible NSP in the list */ + for (iter = priv->nsp_list; iter; iter = g_slist_next (iter)) { + if (!strcmp (specific_object, nm_wimax_nsp_get_dbus_path (NM_WIMAX_NSP (iter->data)))) { + nsp = NM_WIMAX_NSP (iter->data); + break; + } + } + + if (!nsp) { + g_set_error (error, + NM_WIMAX_ERROR, + NM_WIMAX_ERROR_NSP_NOT_FOUND, + "The NSP %s was not in the scan list.", + specific_object); + return FALSE; + } + + nsp_name = nm_wimax_nsp_get_name (nsp); + } + + /* Add a WiMAX setting if one doesn't exist */ + if (!s_wimax) { + s_wimax = (NMSettingWimax *) nm_setting_wimax_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wimax)); + } + + g_assert (nsp_name); + format = g_strdup_printf ("%s %%d", nsp_name); + nm_utils_complete_generic (connection, + NM_SETTING_WIMAX_SETTING_NAME, + existing_connections, + format, + nsp_name, + TRUE); + g_free (format); + g_object_set (G_OBJECT (s_wimax), NM_SETTING_WIMAX_NETWORK_NAME, nsp_name, NULL); + + setting_mac = nm_setting_wimax_get_mac_address (s_wimax); + hw_address = nm_device_get_hw_address (device, NULL); + if (setting_mac) { + /* Make sure the setting MAC (if any) matches the device's permanent MAC */ + if (memcmp (setting_mac->data, hw_address, ETH_ALEN)) { + g_set_error (error, + NM_SETTING_WIMAX_ERROR, + NM_SETTING_WIMAX_ERROR_INVALID_PROPERTY, + NM_SETTING_WIMAX_MAC_ADDRESS); + return FALSE; + } + } else { + GByteArray *mac; + const guint8 null_mac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + + /* Lock the connection to this device by default */ + if (memcmp (hw_address, null_mac, ETH_ALEN)) { + mac = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (mac, hw_address, ETH_ALEN); + g_object_set (G_OBJECT (s_wimax), NM_SETTING_WIMAX_MAC_ADDRESS, mac, NULL); + g_byte_array_free (mac, TRUE); + } + } + + return TRUE; +} + +static gboolean +can_auto_connect (NMDevice *device, + NMConnection *connection, + char **specific_object) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device); + GSList *iter; + + if (!NM_DEVICE_CLASS (nm_device_wimax_parent_class)->can_auto_connect (device, connection, specific_object)) + return FALSE; + + for (iter = priv->nsp_list; iter; iter = iter->next) { + NMWimaxNsp *nsp = NM_WIMAX_NSP (iter->data); + + if (nm_wimax_nsp_check_compatible (nsp, connection)) { + *specific_object = (char *) nm_wimax_nsp_get_dbus_path (nsp); + return TRUE; + } + } + + return FALSE; +} + +static gboolean +is_available (NMDevice *device) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device); + const char *iface = nm_device_get_iface (device); + + if (!priv->enabled) { + nm_log_dbg (LOGD_WIMAX, "(%s): not available because not enabled", iface); + return FALSE; + } + + if (!priv->wimaxd_enabled) { + nm_log_dbg (LOGD_WIMAX, "(%s): not available because not enabled in wimaxd", iface); + return FALSE; + } + + if (!nm_wimax_util_sdk_is_initialized ()) { + nm_log_dbg (LOGD_WIMAX, "(%s): not available because WiMAX SDK not initialized", iface); + return FALSE; + } + + if (!priv->sdk) { + nm_log_dbg (LOGD_WIMAX, "(%s): not available because not known to WiMAX SDK", iface); + return FALSE; + } + + return iwmxsdk_status_get (priv->sdk) >= WIMAX_API_DEVICE_STATUS_Ready; +} + +static void +clear_activation_timeout (NMDeviceWimax *self) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + if (priv->activation_timeout_id) { + g_source_remove (priv->activation_timeout_id); + priv->activation_timeout_id = 0; + } + + priv->connect_failed = FALSE; +} + +static void +clear_link_timeout (NMDeviceWimax *self) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + if (priv->link_timeout_id) { + g_source_remove (priv->link_timeout_id); + priv->link_timeout_id = 0; + } +} + +static void +clear_connected_poll (NMDeviceWimax *self) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + if (priv->poll_id) { + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device); + NMActRequest *req; + GSList *iter; + const char *path; + NMWimaxNsp *nsp = NULL; + + clear_link_timeout (NM_DEVICE_WIMAX (device)); + + *reason = NM_DEVICE_STATE_REASON_NONE; + + req = nm_device_get_act_request (device); + if (!req) + return NM_ACT_STAGE_RETURN_FAILURE; + + path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req)); + if (!path) + return NM_ACT_STAGE_RETURN_FAILURE; + + /* Find the NSP in the scan list */ + for (iter = priv->nsp_list; iter; iter = iter->next) { + NMWimaxNsp *candidate = NM_WIMAX_NSP (iter->data); + + if (!strcmp (path, nm_wimax_nsp_get_dbus_path (candidate))) { + nsp = candidate; + break; + } + } + + /* Couldn't find the NSP for some reason */ + if (nsp == NULL) + return NM_ACT_STAGE_RETURN_FAILURE; + + set_current_nsp (NM_DEVICE_WIMAX (device), nsp); + + priv->prepare_done = TRUE; + + /* If the device is scanning, it won't connect, so we have to wait until + * it's not scanning to proceed to stage 2. + */ + if (priv->status == WIMAX_API_DEVICE_STATUS_Scanning) + return NM_ACT_STAGE_RETURN_POSTPONE; + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage2_config (NMDevice *device, NMDeviceStateReason *reason) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device); + NMConnection *connection; + NMSettingWimax *s_wimax; + const char *nsp_name, *iface; + int ret; + + iface = nm_device_get_iface (device); + g_assert (iface); + + connection = nm_device_get_connection (device); + g_assert (connection); + + s_wimax = nm_connection_get_setting_wimax (connection); + g_assert (s_wimax); + + nsp_name = nm_setting_wimax_get_network_name (s_wimax); + g_assert (nsp_name); + + nm_log_info (LOGD_WIMAX, "(%s): connecting to NSP '%s'", + iface, nsp_name); + + priv->connect_failed = FALSE; + ret = iwmx_sdk_connect (priv->sdk, nsp_name); + if (ret < 0 && ret != -EINPROGRESS) { + nm_log_err (LOGD_WIMAX, "(%s): failed to connect to NSP '%s'", + iface, nsp_name); + *reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* FIXME: Is 40 seconds good estimation? I have no idea */ + priv->activation_timeout_id = g_timeout_add_seconds (40, activation_timed_out, device); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +force_disconnect (NMDeviceWimax *self, struct wmxsdk *sdk) +{ + WIMAX_API_DEVICE_STATUS status; + int ret; + const char *iface; + + g_return_if_fail (sdk != NULL); + + iface = nm_device_get_iface (NM_DEVICE (self)); + + status = iwmxsdk_status_get (sdk); + if ((int) status < 0) { + nm_log_err (LOGD_WIMAX, "(%s): failed to read WiMAX device status: %d", + iface, status); + return; + } + + if ( status == WIMAX_API_DEVICE_STATUS_Connecting + || status == WIMAX_API_DEVICE_STATUS_Data_Connected) { + nm_log_dbg (LOGD_WIMAX, "(%s): requesting disconnect", iface); + ret = iwmx_sdk_disconnect (sdk); + if (ret < 0 && ret != -EINPROGRESS) { + nm_log_err (LOGD_WIMAX, "(%s): failed to disconnect WiMAX device: %d", + iface, ret); + } + } +} + +static void +deactivate (NMDevice *device) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (device); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + clear_activation_timeout (self); + clear_link_timeout (self); + clear_connected_poll (self); + + set_current_nsp (self, NULL); + + if (priv->sdk) { + /* Read explicit status here just to make sure we have the most + * up-to-date status and to ensure we disconnect if needed. + */ + force_disconnect (self, priv->sdk); + } +} + +/*************************************************************************/ + +static void +wmx_state_change_cb (struct wmxsdk *wmxsdk, + WIMAX_API_DEVICE_STATUS new_status, + WIMAX_API_DEVICE_STATUS old_status, + WIMAX_API_STATUS_REASON reason, + WIMAX_API_CONNECTION_PROGRESS_INFO progress, + void *user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + NMDeviceState state; + const char *iface; + gboolean old_available = FALSE; + const char *nsp_name = NULL; + + iface = nm_device_get_iface (NM_DEVICE (self)); + nm_log_info (LOGD_WIMAX, "(%s): wimax state change %s -> %s (%s (%d))", + iface, + iwmx_sdk_dev_status_to_str (old_status), + iwmx_sdk_dev_status_to_str (new_status), + iwmx_sdk_con_progress_to_str (progress), + progress); + + if (new_status == old_status) + return; + + state = nm_device_get_state (NM_DEVICE (self)); + old_available = nm_device_is_available (NM_DEVICE (self)); + + priv->status = new_status; + if (priv->current_nsp) + nsp_name = nm_wimax_nsp_get_name (priv->current_nsp); + + switch (new_status) { + case WIMAX_API_DEVICE_STATUS_UnInitialized: + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW_SW: + case WIMAX_API_DEVICE_STATUS_RF_OFF_HW: + case WIMAX_API_DEVICE_STATUS_RF_OFF_SW: + if (priv->wimaxd_enabled) { + priv->wimaxd_enabled = FALSE; + if (update_availability (self, old_available)) + return; + } + break; + case WIMAX_API_DEVICE_STATUS_Connecting: + case WIMAX_API_DEVICE_STATUS_Data_Connected: + /* If for some reason we're initially connected, force a disconnect here */ + if (state < NM_DEVICE_STATE_DISCONNECTED) + force_disconnect (self, wmxsdk); + /* Fall through */ + case WIMAX_API_DEVICE_STATUS_Ready: + case WIMAX_API_DEVICE_STATUS_Scanning: + if (priv->wimaxd_enabled == FALSE) { + priv->wimaxd_enabled = TRUE; + if (update_availability (self, old_available)) + return; + } + break; + default: + nm_log_warn (LOGD_WIMAX, "(%s): unhandled WiMAX device state %d", + iface, new_status); + break; + } + + /* Handle activation success and failure */ + if (nm_device_is_activating (NM_DEVICE (self))) { + if (new_status == WIMAX_API_DEVICE_STATUS_Data_Connected) { + /* Success */ + clear_activation_timeout (self); + + nm_log_info (LOGD_WIMAX, "(%s): connected to '%s'", + iface, nsp_name); + nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self)); + return; + } + + if (priv->connect_failed) { + /* Connection attempt failed */ + nm_log_info (LOGD_WIMAX, "(%s): connection to '%s' failed: (%d) %s", + iface, nsp_name, reason, iwmx_sdk_reason_to_str (reason)); + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return; + } + + /* If stage2 was postponed because the device was scanning or something, + * then check if we need to move to stage2 now that the device might be + * ready. + */ + if (state == NM_DEVICE_STATE_PREPARE && priv->prepare_done) { + if ( new_status == WIMAX_API_DEVICE_STATUS_Ready + || new_status == WIMAX_API_DEVICE_STATUS_Connecting) { + nm_device_activate_schedule_stage2_device_config (NM_DEVICE (self)); + return; + } + } + } + + /* Handle disconnection */ + if (state == NM_DEVICE_STATE_ACTIVATED) { + if ( old_status == WIMAX_API_DEVICE_STATUS_Data_Connected + && new_status < WIMAX_API_DEVICE_STATUS_Connecting) { + + nm_log_info (LOGD_WIMAX, "(%s): disconnected from '%s': (%d) %s", + iface, nsp_name, reason, iwmx_sdk_reason_to_str (reason)); + + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_CONFIG_FAILED); + } + } +} + +static gboolean +link_timeout_cb (gpointer user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + priv->link_timeout_id = 0; + + nm_log_dbg (LOGD_WIMAX, "(%s): link timed out", nm_device_get_iface (NM_DEVICE (self))); + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_CARRIER); + + return FALSE; +} + +static void +wmx_media_status_cb (struct wmxsdk *wmxsdk, + WIMAX_API_MEDIA_STATUS new_status, + void *user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + NMDeviceState state; + const char *iface; + + iface = nm_device_get_iface (NM_DEVICE (self)); + state = nm_device_get_state (NM_DEVICE (self)); + + nm_log_dbg (LOGD_WIMAX, "(%s): media status change to %s", + iface, iwmx_sdk_media_status_to_str (new_status)); + + /* We only care about media events while activated */ + if (state != NM_DEVICE_STATE_ACTIVATED) + return; + + clear_link_timeout (self); + + switch (new_status) { + case WIMAX_API_MEDIA_STATUS_LINK_UP: + break; + case WIMAX_API_MEDIA_STATUS_LINK_DOWN: + nm_log_dbg (LOGD_WIMAX, "(%s): starting link timeout", iface); + priv->link_timeout_id = g_timeout_add_seconds (15, link_timeout_cb, self); + break; + case WIMAX_API_MEDIA_STATUS_LINK_RENEW: + nm_log_dbg (LOGD_WIMAX, "(%s): renewing DHCP lease", iface); + if (!nm_device_dhcp4_renew (NM_DEVICE (self), TRUE)) { + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_FAILED); + } + break; + default: + nm_log_err (LOGD_WIMAX, "(%s): unhandled media status %d", iface, new_status); + break; + } +} + +static void +wmx_connect_result_cb (struct wmxsdk *wmxsdk, + WIMAX_API_NETWORK_CONNECTION_RESP result, + void *user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + if (nm_device_is_activating (NM_DEVICE (self))) { + priv->connect_failed = (result == WIMAX_API_CONNECTION_SUCCESS); + /* Wait for the state change so we can get the reason code; we + * cache the connect failure so we don't have to wait for the + * activation timeout. + */ + } +} + +static void +remove_outdated_nsps (NMDeviceWimax *self, + WIMAX_API_NSP_INFO_EX *nsp_list, + guint32 list_size) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + GSList *iter; + GSList *to_remove = NULL; + + for (iter = priv->nsp_list; iter; iter = iter->next) { + NMWimaxNsp *nsp = NM_WIMAX_NSP (iter->data); + gboolean found = FALSE; + int i; + + for (i = 0; i < list_size; i++) { + WIMAX_API_NSP_INFO_EX *info = &nsp_list[i]; + + if (!g_strcmp0 (nm_wimax_nsp_get_name (nsp), (char *) info->NSPName)) { + found = TRUE; + break; + } + } + + if (!found) + to_remove = g_slist_prepend (to_remove, nsp); + } + + for (iter = to_remove; iter; iter = iter->next) { + NMWimaxNsp *nsp = NM_WIMAX_NSP (iter->data); + + emit_nsp_added_removed (self, NSP_REMOVED, nsp, FALSE); + priv->nsp_list = g_slist_remove (priv->nsp_list, nsp); + g_object_unref (nsp); + } + + if (g_slist_length(to_remove) > 0) + nm_device_recheck_available_connections (NM_DEVICE (self)); + + g_slist_free (to_remove); +} + +static void +wmx_scan_result_cb (struct wmxsdk *wmxsdk, + WIMAX_API_NSP_INFO_EX *nsps, + guint num_nsps, + void *user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + const char *iface = nm_device_get_iface (NM_DEVICE (self)); + int i; + + remove_outdated_nsps (self, nsps, num_nsps); + + /* Add new NSPs and update existing ones */ + for (i = 0; i < num_nsps; i++) { + WIMAX_API_NSP_INFO_EX *sdk_nsp = &nsps[i]; + const char *nsp_name = (const char *) sdk_nsp->NSPName; + NMWimaxNspNetworkType net_type; + guint signalq; + NMWimaxNsp *nsp; + gboolean new_nsp; + + nsp = get_nsp_by_name (self, nsp_name); + new_nsp = (nsp == NULL); + if (new_nsp) { + nsp = nm_wimax_nsp_new (nsp_name); + nm_log_dbg (LOGD_WIMAX, "(%s): new WiMAX NSP '%s'", iface, nsp_name); + } + + net_type = nm_wimax_util_convert_network_type (sdk_nsp->networkType); + if (net_type != nm_wimax_nsp_get_network_type (nsp)) + g_object_set (nsp, NM_WIMAX_NSP_NETWORK_TYPE, net_type, NULL); + + signalq = CLAMP (sdk_nsp->linkQuality, 0, 100); + if (signalq != nm_wimax_nsp_get_signal_quality (nsp)) + g_object_set (nsp, NM_WIMAX_NSP_SIGNAL_QUALITY, signalq, NULL); + + nm_log_dbg (LOGD_WIMAX, "(%s): WiMAX NSP '%s' quality %d%% type %d", + iface, nsp_name, sdk_nsp->linkQuality, net_type); + + if (new_nsp) { + priv->nsp_list = g_slist_append (priv->nsp_list, nsp); + nm_wimax_nsp_export_to_dbus (nsp); + emit_nsp_added_removed (self, NSP_ADDED, nsp, TRUE); + } + } +} + +static void +wmx_removed_cb (struct wmxsdk *wmxsdk, void *user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + if (!priv->sdk) { + nm_log_dbg (LOGD_WIMAX, "(%s): removed unhandled WiMAX interface", wmxsdk->ifname); + return; + } + + nm_log_dbg (LOGD_WIMAX, "(%s): removed WiMAX interface", wmxsdk->ifname); + + /* Clear callbacks just in case we don't hold the last reference */ + iwmx_sdk_set_callbacks (priv->sdk, NULL, NULL, NULL, NULL, NULL, NULL); + wmxsdk_unref (priv->sdk); + priv->sdk = NULL; + + priv->status = WIMAX_API_DEVICE_STATUS_UnInitialized; + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); +} + +/*************************************************************************/ + +static inline gint +sdk_rssi_to_dbm (guint raw_rssi) +{ + /* Values range from 0x00 to 0x53, where -123dBm is encoded as 0x00 and + * -40dBm encoded as 0x53 in 1dB increments. + */ + return raw_rssi - 123; +} + +static inline gint +sdk_cinr_to_db (guint raw_cinr) +{ + /* Values range from 0x00 to 0x3F, where -10dB is encoded as 0x00 and + * 53dB encoded as 0x3F in 1dB increments. + */ + return raw_cinr - 10; +} + +static inline gint +sdk_tx_pow_to_dbm (guint raw_tx_pow) +{ + /* Values range from 0x00 to 0xFF, where -84dBm is encoded as 0x00 and + * 43.5dBm is encoded as 0xFF in 0.5dB increments. Normalize so that + * 0 dBm == 0. + */ + return (int) (((double) raw_tx_pow / 2.0) - 84) * 2; +} + +static void +set_link_status (NMDeviceWimax *self, WIMAX_API_LINK_STATUS_INFO_EX *link_status) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + guint center_freq = 0; + gint conv_rssi = 0, conv_cinr = 0, conv_tx_pow = 0; + char *new_bsid = NULL; + + if (link_status) { + center_freq = link_status->centerFrequency; + conv_rssi = sdk_rssi_to_dbm (link_status->RSSI); + conv_cinr = sdk_cinr_to_db (link_status->CINR); + conv_tx_pow = sdk_tx_pow_to_dbm (link_status->txPWR); + new_bsid = nm_utils_hwaddr_ntoa_len (link_status->bsId, 6); + } + + if (priv->center_freq != center_freq) { + priv->center_freq = center_freq; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIMAX_CENTER_FREQUENCY); + } + + if (priv->rssi != conv_rssi) { + priv->rssi = conv_rssi; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIMAX_RSSI); + } + + if (priv->cinr != conv_cinr) { + priv->cinr = conv_cinr; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIMAX_CINR); + } + + if (priv->tx_power != conv_tx_pow) { + priv->tx_power = conv_tx_pow; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIMAX_TX_POWER); + } + + if (g_strcmp0 (priv->bsid, new_bsid) != 0) { + g_free (priv->bsid); + priv->bsid = new_bsid; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIMAX_BSID); + } else + g_free (new_bsid); +} + +static gboolean +connected_poll_cb (gpointer user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + WIMAX_API_CONNECTED_NSP_INFO_EX *sdk_nsp; + WIMAX_API_LINK_STATUS_INFO_EX *link_status; + + g_return_val_if_fail (priv->sdk != NULL, FALSE); + + /* Get details of the connected NSP */ + sdk_nsp = iwmx_sdk_get_connected_network (priv->sdk); + if (sdk_nsp) { + const char *nsp_name = (const char *) sdk_nsp->NSPName; + NMWimaxNsp *nsp; + + nsp = get_nsp_by_name (self, nsp_name); + if (nsp) { + NMWimaxNspNetworkType net_type; + guint signalq; + + net_type = nm_wimax_util_convert_network_type (sdk_nsp->networkType); + if (net_type != nm_wimax_nsp_get_network_type (nsp)) + g_object_set (nsp, NM_WIMAX_NSP_NETWORK_TYPE, net_type, NULL); + + signalq = sdk_nsp->linkQuality; + if (signalq != nm_wimax_nsp_get_signal_quality (nsp)) + g_object_set (nsp, NM_WIMAX_NSP_SIGNAL_QUALITY, signalq, NULL); + + nm_log_dbg (LOGD_WIMAX, "(%s): WiMAX NSP '%s' quality %d%% type %d", + nm_device_get_iface (NM_DEVICE (self)), + nsp_name, sdk_nsp->linkQuality, net_type); + } + free (sdk_nsp); + } + + /* Get details of the current radio link */ + link_status = iwmx_sdk_get_link_status_info (priv->sdk); + if (link_status) { + set_link_status (self, link_status); + free (link_status); + } + + return TRUE; /* reschedule */ +} + +static void +device_state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (device); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + /* Reset our stage1 (Prepare) done marker since it's only valid while in stage1 */ + priv->prepare_done = FALSE; + + if (new_state < NM_DEVICE_STATE_DISCONNECTED) + remove_all_nsps (self); + + /* Request initial NSP list when device is first started */ + if ( new_state == NM_DEVICE_STATE_DISCONNECTED + && old_state < NM_DEVICE_STATE_DISCONNECTED) { + if (priv->sdk) + iwmx_sdk_get_networks (priv->sdk); + } + + if (new_state == NM_DEVICE_STATE_FAILED || new_state <= NM_DEVICE_STATE_DISCONNECTED) { + set_current_nsp (self, NULL); + clear_activation_timeout (self); + } + + if (new_state == NM_DEVICE_STATE_ACTIVATED) { + /* poll link quality and BSID */ + clear_connected_poll (self); + priv->poll_id = g_timeout_add_seconds (10, connected_poll_cb, self); + connected_poll_cb (self); + } else { + clear_link_timeout (self); + clear_connected_poll (self); + set_link_status (self, NULL); + } +} + +/*************************************************************************/ + +static gboolean +sdk_action_defer_cb (gpointer user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + gboolean old_available = nm_device_is_available (NM_DEVICE (self)); + + NM_DEVICE_WIMAX_GET_PRIVATE (self)->sdk_action_defer_id = 0; + update_availability (self, old_available); + return FALSE; +} + +static void +wmx_new_sdk_cb (struct wmxsdk *sdk, void *user_data) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (user_data); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + /* We only track one wmxsdk at a time because the WiMAX SDK is pretty stupid */ + if (priv->sdk) { + nm_log_dbg (LOGD_WIMAX, "(%s): WiMAX interface already known", sdk->ifname); + return; + } + + nm_log_dbg (LOGD_WIMAX, "(%s): new WiMAX interface (%s)", sdk->ifname, sdk->name); + + /* Now that we have an SDK, schedule an idle handler to start the device up */ + priv->sdk = wmxsdk_ref (sdk); + iwmx_sdk_set_callbacks(priv->sdk, + wmx_state_change_cb, + wmx_media_status_cb, + wmx_connect_result_cb, + wmx_scan_result_cb, + wmx_removed_cb, + self); + iwmx_sdk_set_fast_reconnect_enabled (priv->sdk, 0); + + if (!priv->sdk_action_defer_id) + priv->sdk_action_defer_id = g_idle_add (sdk_action_defer_cb, self); +} + +/*************************************************************************/ + +NMDevice * +nm_device_wimax_new (NMPlatformLink *platform_device) +{ + NMDevice *device; + + g_return_val_if_fail (platform_device != NULL, NULL); + + device = (NMDevice *) g_object_new (NM_TYPE_DEVICE_WIMAX, + NM_DEVICE_PLATFORM_DEVICE, platform_device, + NM_DEVICE_TYPE_DESC, "WiMAX", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIMAX, + NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WIMAX, + NULL); + if (device) { + struct wmxsdk *sdk; + + nm_wimax_util_sdk_ref (); + + /* See if the SDK already knows about this interface */ + sdk = iwmx_sdk_get_wmxsdk_for_iface (platform_device->name); + if (sdk) + wmx_new_sdk_cb (sdk, device); + + /* If it doesn't, we want to be notified when it does */ + iwmx_sdk_new_callback_register (wmx_new_sdk_cb, device); + } + + return device; +} + +static void +nm_device_wimax_init (NMDeviceWimax *self) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + priv->status = WIMAX_API_DEVICE_STATUS_UnInitialized; +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (object); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + GPtrArray *array; + GSList *iter; + + switch (prop_id) { + case PROP_NSPS: + array = g_ptr_array_sized_new (4); + for (iter = priv->nsp_list; iter; iter = g_slist_next (iter)) + g_ptr_array_add (array, g_strdup (nm_wimax_nsp_get_dbus_path (NM_WIMAX_NSP (iter->data)))); + g_value_take_boxed (value, array); + break; + case PROP_ACTIVE_NSP: + if (priv->current_nsp) + g_value_set_boxed (value, nm_wimax_nsp_get_dbus_path (priv->current_nsp)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_CENTER_FREQ: + g_value_set_uint (value, priv->center_freq); + break; + case PROP_RSSI: + g_value_set_int (value, priv->rssi); + break; + case PROP_CINR: + g_value_set_int (value, priv->cinr); + break; + case PROP_TX_POWER: + g_value_set_int (value, priv->tx_power); + break; + case PROP_BSID: + g_value_set_string (value, priv->bsid); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMDeviceWimax *self = NM_DEVICE_WIMAX (object); + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + + if (priv->disposed) + goto done; + + priv->disposed = TRUE; + + clear_activation_timeout (self); + clear_link_timeout (self); + clear_connected_poll (self); + + if (priv->sdk_action_defer_id) + g_source_remove (priv->sdk_action_defer_id); + + if (priv->sdk) { + iwmx_sdk_set_callbacks (priv->sdk, NULL, NULL, NULL, NULL, NULL, NULL); + wmxsdk_unref (priv->sdk); + } + + g_free (priv->bsid); + + set_current_nsp (self, NULL); + + g_slist_free_full (priv->nsp_list, g_object_unref); + + iwmx_sdk_new_callback_unregister (wmx_new_sdk_cb, self); + nm_wimax_util_sdk_unref (); + +done: + G_OBJECT_CLASS (nm_device_wimax_parent_class)->dispose (object); +} + +static void +nm_device_wimax_class_init (NMDeviceWimaxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceWimaxPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->can_auto_connect = can_auto_connect; + device_class->is_available = is_available; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->deactivate = deactivate; + device_class->set_enabled = set_enabled; + + device_class->state_changed = device_state_changed; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_NSPS, + g_param_spec_boxed (NM_DEVICE_WIMAX_NSPS, + "Network access points", + "Network access points", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_ACTIVE_NSP, + g_param_spec_boxed (NM_DEVICE_WIMAX_ACTIVE_NSP, + "Active NSP", + "Currently active NSP", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_CENTER_FREQ, + g_param_spec_uint (NM_DEVICE_WIMAX_CENTER_FREQUENCY, + "Center frequency", + "Center frequency", + 0, G_MAXUINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_RSSI, + g_param_spec_int (NM_DEVICE_WIMAX_RSSI, + "RSSI", + "RSSI", + G_MININT, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_CINR, + g_param_spec_int (NM_DEVICE_WIMAX_CINR, + "CINR", + "CINR", + G_MININT, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_TX_POWER, + g_param_spec_int (NM_DEVICE_WIMAX_TX_POWER, + "TX Power", + "TX Power", + G_MININT, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_BSID, + g_param_spec_string (NM_DEVICE_WIMAX_BSID, + "BSID", + "BSID", + NULL, + G_PARAM_READABLE)); + + /* Signals */ + signals[NSP_ADDED] = + g_signal_new ("nsp-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWimaxClass, nsp_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + signals[NSP_REMOVED] = + g_signal_new ("nsp-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWimaxClass, nsp_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_device_wimax_object_info); + + dbus_g_error_domain_register (NM_WIMAX_ERROR, NULL, NM_TYPE_WIMAX_ERROR); +} diff --git a/src/devices/wimax/nm-device-wimax.h b/src/devices/wimax/nm-device-wimax.h new file mode 100644 index 000000000..8b252ee16 --- /dev/null +++ b/src/devices/wimax/nm-device-wimax.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2010 - 2011 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef NM_DEVICE_WIMAX_H +#define NM_DEVICE_WIMAX_H + +#include <net/ethernet.h> +#include "nm-device.h" +#include "nm-wimax-nsp.h" + +G_BEGIN_DECLS + +#define NM_TYPE_DEVICE_WIMAX (nm_device_wimax_get_type ()) +#define NM_DEVICE_WIMAX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIMAX, NMDeviceWimax)) +#define NM_DEVICE_WIMAX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_WIMAX, NMDeviceWimaxClass)) +#define NM_IS_DEVICE_WIMAX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIMAX)) +#define NM_IS_DEVICE_WIMAX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_WIMAX)) +#define NM_DEVICE_WIMAX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_WIMAX, NMDeviceWimaxClass)) + +typedef enum +{ + NM_WIMAX_ERROR_CONNECTION_NOT_WIMAX = 0, /*< nick=ConnectionNotWimax >*/ + NM_WIMAX_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_WIMAX_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ + NM_WIMAX_ERROR_NSP_NOT_FOUND, /*< nick=NspNotFound >*/ +} NMWimaxError; + +#define NM_DEVICE_WIMAX_NSPS "nsps" +#define NM_DEVICE_WIMAX_ACTIVE_NSP "active-nsp" +#define NM_DEVICE_WIMAX_CENTER_FREQUENCY "center-frequency" +#define NM_DEVICE_WIMAX_RSSI "rssi" +#define NM_DEVICE_WIMAX_CINR "cinr" +#define NM_DEVICE_WIMAX_TX_POWER "tx-power" +#define NM_DEVICE_WIMAX_BSID "bsid" + +typedef struct { + NMDevice parent; +} NMDeviceWimax; + +typedef struct { + NMDeviceClass parent; + + /* Signals */ + void (*nsp_added) (NMDeviceWimax *wimax, NMWimaxNsp *nsp); + void (*nsp_removed) (NMDeviceWimax *wimax, NMWimaxNsp *nsp); + void (*properties_changed) (NMDeviceWimax *wimax, GHashTable *properties); +} NMDeviceWimaxClass; + +GType nm_device_wimax_get_type (void); + +NMDevice *nm_device_wimax_new (NMPlatformLink *platform_device); + +G_END_DECLS + +#endif /* NM_DEVICE_WIMAX_H */ diff --git a/src/devices/wimax/nm-wimax-factory.c b/src/devices/wimax/nm-wimax-factory.c new file mode 100644 index 000000000..ca962e686 --- /dev/null +++ b/src/devices/wimax/nm-wimax-factory.c @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2011 - 2014 Red Hat, Inc. + */ + +#include <gmodule.h> + +#include "nm-device-factory.h" +#include "nm-device-wimax.h" + +#define NM_TYPE_WIMAX_FACTORY (nm_wimax_factory_get_type ()) +#define NM_WIMAX_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_WIMAX_FACTORY, NMWimaxFactory)) + +typedef struct { + GObject parent; +} NMWimaxFactory; + +typedef struct { + GObjectClass parent; +} NMWimaxFactoryClass; + +static GType nm_wimax_factory_get_type (void); + +static void device_factory_interface_init (NMDeviceFactory *factory_iface); + +G_DEFINE_TYPE_EXTENDED (NMWimaxFactory, nm_wimax_factory, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_DEVICE_FACTORY, device_factory_interface_init)) + +/**************************************************************************/ + +#define PLUGIN_TYPE NM_DEVICE_TYPE_WIMAX + +G_MODULE_EXPORT NMDeviceFactory * +nm_device_factory_create (GError **error) +{ + return (NMDeviceFactory *) g_object_new (NM_TYPE_WIMAX_FACTORY, NULL); +} + +G_MODULE_EXPORT NMDeviceType +nm_device_factory_get_device_type (void) +{ + return PLUGIN_TYPE; +} + +/**************************************************************************/ + +static NMDevice * +new_link (NMDeviceFactory *factory, NMPlatformLink *plink, GError **error) +{ + /* FIXME: check udev 'DEVTYPE' instead; but since we only support Intel + * WiMAX devices for now this is appropriate. + */ + if (g_strcmp0 (plink->driver, "i2400m_usb") != 0) + return NULL; /* unsupported */ + + return (NMDevice *) nm_device_wimax_new (plink); +} + +static void +device_factory_interface_init (NMDeviceFactory *factory_iface) +{ + factory_iface->new_link = new_link; +} + +static void +nm_wimax_factory_init (NMWimaxFactory *factory) +{ +} + +static void +nm_wimax_factory_class_init (NMWimaxFactoryClass *wf_class) +{ +} + diff --git a/src/devices/wimax/nm-wimax-nsp.c b/src/devices/wimax/nm-wimax-nsp.c new file mode 100644 index 000000000..efe3b0587 --- /dev/null +++ b/src/devices/wimax/nm-wimax-nsp.c @@ -0,0 +1,242 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2010 - 2012 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#include "nm-wimax-nsp.h" +#include "NetworkManager.h" +#include "nm-dbus-manager.h" +#include "nm-setting-wimax.h" +#include "nm-wimax-nsp-glue.h" +#include "nm-utils.h" + +G_DEFINE_TYPE (NMWimaxNsp, nm_wimax_nsp, G_TYPE_OBJECT) + +enum { + PROP_0, + + PROP_NAME, + PROP_SIGNAL_QUALITY, + PROP_NETWORK_TYPE, + + LAST_PROP +}; + +#define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_WIMAX_NSP, NMWimaxNspPrivate)) + +typedef struct { + char *dbus_path; + + char *name; + guint32 signal_quality; + NMWimaxNspNetworkType network_type; +} NMWimaxNspPrivate; + +NMWimaxNsp * +nm_wimax_nsp_new (const char *name) +{ + g_return_val_if_fail (name != NULL, NULL); + + return NM_WIMAX_NSP (g_object_new (NM_TYPE_WIMAX_NSP, + NM_WIMAX_NSP_NAME, name, + NULL)); +} + +const char * +nm_wimax_nsp_get_name (NMWimaxNsp *self) +{ + g_return_val_if_fail (NM_IS_WIMAX_NSP (self), NULL); + + return GET_PRIVATE (self)->name; +} + +guint32 +nm_wimax_nsp_get_signal_quality (NMWimaxNsp *self) +{ + g_return_val_if_fail (NM_IS_WIMAX_NSP (self), 0); + + return GET_PRIVATE (self)->signal_quality; +} + +NMWimaxNspNetworkType +nm_wimax_nsp_get_network_type (NMWimaxNsp *self) +{ + g_return_val_if_fail (NM_IS_WIMAX_NSP (self), 0); + + return GET_PRIVATE (self)->network_type; +} + +void +nm_wimax_nsp_export_to_dbus (NMWimaxNsp *self) +{ + NMWimaxNspPrivate *priv; + static guint32 counter = 0; + + g_return_if_fail (NM_IS_WIMAX_NSP (self)); + + priv = GET_PRIVATE (self); + + g_return_if_fail (priv->dbus_path == NULL); + + priv->dbus_path = g_strdup_printf (NM_DBUS_PATH_WIMAX_NSP "/%d", counter++); + nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->dbus_path, self); +} + +const char * +nm_wimax_nsp_get_dbus_path (NMWimaxNsp *self) +{ + g_return_val_if_fail (NM_IS_WIMAX_NSP (self), NULL); + + return GET_PRIVATE (self)->dbus_path; +} + +gboolean +nm_wimax_nsp_check_compatible (NMWimaxNsp *self, + NMConnection *connection) +{ + NMWimaxNspPrivate *priv; + NMSettingWimax *s_wimax; + + g_return_val_if_fail (NM_IS_WIMAX_NSP (self), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + + priv = GET_PRIVATE (self); + + s_wimax = nm_connection_get_setting_wimax (connection); + if (!s_wimax) + return FALSE; + + return g_strcmp0 (nm_wimax_nsp_get_name (self), nm_setting_wimax_get_network_name (s_wimax)) == 0; +} + +static void +nm_wimax_nsp_init (NMWimaxNsp *self) +{ +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMWimaxNspPrivate *priv = GET_PRIVATE (object); + guint32 quality; + guint network_type; + + switch (prop_id) { + case PROP_NAME: + /* Construct only */ + priv->name = g_value_dup_string (value); + break; + case PROP_SIGNAL_QUALITY: + quality = g_value_get_uint (value); + if (quality != priv->signal_quality) { + priv->signal_quality = CLAMP (quality, 0, 100); + g_object_notify (object, NM_WIMAX_NSP_SIGNAL_QUALITY); + } + break; + case PROP_NETWORK_TYPE: + network_type = g_value_get_uint (value); + if (network_type != priv->network_type) { + priv->network_type = network_type; + g_object_notify (object, NM_WIMAX_NSP_NETWORK_TYPE); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMWimaxNsp *self = NM_WIMAX_NSP (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, nm_wimax_nsp_get_name (self)); + break; + case PROP_SIGNAL_QUALITY: + g_value_set_uint (value, nm_wimax_nsp_get_signal_quality (self)); + break; + case PROP_NETWORK_TYPE: + g_value_set_uint (value, nm_wimax_nsp_get_network_type (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + NMWimaxNspPrivate *priv = GET_PRIVATE (object); + + g_free (priv->name); + g_free (priv->dbus_path); + + G_OBJECT_CLASS (nm_wimax_nsp_parent_class)->finalize (object); +} + +static void +nm_wimax_nsp_class_init (NMWimaxNspClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMWimaxNspPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + g_object_class_install_property + (object_class, PROP_NAME, + g_param_spec_string (NM_WIMAX_NSP_NAME, + "Name", + "Name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_SIGNAL_QUALITY, + g_param_spec_uint (NM_WIMAX_NSP_SIGNAL_QUALITY, + "SignalQuality", + "SignalQuality", + 0, + 100, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_NETWORK_TYPE, + g_param_spec_uint (NM_WIMAX_NSP_NETWORK_TYPE, + "NetworkType", + "NetworkType", + NM_WIMAX_NSP_NETWORK_TYPE_UNKNOWN, + NM_WIMAX_NSP_NETWORK_TYPE_ROAMING_PARTNER, + NM_WIMAX_NSP_NETWORK_TYPE_UNKNOWN, + G_PARAM_READWRITE)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (klass), + &dbus_glib_nm_wimax_nsp_object_info); +} diff --git a/src/devices/wimax/nm-wimax-nsp.h b/src/devices/wimax/nm-wimax-nsp.h new file mode 100644 index 000000000..a74b68a79 --- /dev/null +++ b/src/devices/wimax/nm-wimax-nsp.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef NM_WIMAX_NSP_H +#define NM_WIMAX_NSP_H + +#include <glib-object.h> +#include "nm-wimax-types.h" +#include "nm-connection.h" + +#define NM_TYPE_WIMAX_NSP (nm_wimax_nsp_get_type ()) +#define NM_WIMAX_NSP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIMAX_NSP, NMWimaxNsp)) +#define NM_WIMAX_NSP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIMAX_NSP, NMWimaxNspClass)) +#define NM_IS_WIMAX_NSP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIMAX_NSP)) +#define NM_IS_WIMAX_NSP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIMAX_NSP)) +#define NM_WIMAX_NSP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIMAX_NSP, NMWimaxNspClass)) + +#define NM_WIMAX_NSP_NAME "name" +#define NM_WIMAX_NSP_SIGNAL_QUALITY "signal-quality" +#define NM_WIMAX_NSP_NETWORK_TYPE "network-type" + +typedef struct { + GObject parent; +} NMWimaxNsp; + +typedef struct { + GObjectClass parent; + + /* Signals */ + void (*properties_changed) (NMWimaxNsp *nsp, GHashTable *properties); +} NMWimaxNspClass; + +GType nm_wimax_nsp_get_type (void); + +NMWimaxNsp *nm_wimax_nsp_new (const char *name); +const char *nm_wimax_nsp_get_name (NMWimaxNsp *self); +guint32 nm_wimax_nsp_get_signal_quality (NMWimaxNsp *self); +NMWimaxNspNetworkType nm_wimax_nsp_get_network_type (NMWimaxNsp *self); + +void nm_wimax_nsp_export_to_dbus (NMWimaxNsp *self); +const char *nm_wimax_nsp_get_dbus_path (NMWimaxNsp *self); + +gboolean nm_wimax_nsp_check_compatible (NMWimaxNsp *self, + NMConnection *connection); + +#endif /* NM_WIMAX_NSP_H */ diff --git a/src/devices/wimax/nm-wimax-types.h b/src/devices/wimax/nm-wimax-types.h new file mode 100644 index 000000000..8c807fd8a --- /dev/null +++ b/src/devices/wimax/nm-wimax-types.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef NM_WIMAX_TYPES_H +#define NM_WIMAX_TYPES_H + +typedef enum { + NM_WIMAX_NSP_NETWORK_TYPE_UNKNOWN, + NM_WIMAX_NSP_NETWORK_TYPE_HOME, + NM_WIMAX_NSP_NETWORK_TYPE_PARTNER, + NM_WIMAX_NSP_NETWORK_TYPE_ROAMING_PARTNER +} NMWimaxNspNetworkType; + +#endif /* NM_WIMAX_TYPES_H */ diff --git a/src/devices/wimax/nm-wimax-util.c b/src/devices/wimax/nm-wimax-util.c new file mode 100644 index 000000000..bca25a1db --- /dev/null +++ b/src/devices/wimax/nm-wimax-util.c @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 Novell, Inc. + */ + +#include <WiMaxAPI.h> +#include "nm-wimax-util.h" +#include "nm-utils.h" +#include "iwmxsdk.h" +#include "nm-logging.h" + +static guint sdk_refcount = 0; + +void +nm_wimax_util_sdk_ref (void) +{ + int ret = 0; + + if (sdk_refcount == 0) { + ret = iwmx_sdk_api_init (); + if (ret != 0) { + nm_log_warn (LOGD_WIMAX, "Failed to initialize WiMAX: %d", ret); + return; + } + } + sdk_refcount++; +} + +gboolean +nm_wimax_util_sdk_is_initialized (void) +{ + return sdk_refcount > 0; +} + +void +nm_wimax_util_sdk_unref (void) +{ + g_return_if_fail (sdk_refcount > 0); + + sdk_refcount--; + if (sdk_refcount == 0) + iwmx_sdk_api_exit (); +} + +NMWimaxNspNetworkType +nm_wimax_util_convert_network_type (WIMAX_API_NETWORK_TYPE wimax_network_type) +{ + NMWimaxNspNetworkType type; + + switch (wimax_network_type) { + case WIMAX_API_HOME: + type = NM_WIMAX_NSP_NETWORK_TYPE_HOME; + break; + case WIMAX_API_PARTNER: + type = NM_WIMAX_NSP_NETWORK_TYPE_PARTNER; + break; + case WIMAX_API_ROAMING_PARTNER: + type = NM_WIMAX_NSP_NETWORK_TYPE_ROAMING_PARTNER; + break; + default: + type = NM_WIMAX_NSP_NETWORK_TYPE_UNKNOWN; + break; + } + + return type; +} + diff --git a/src/devices/wimax/nm-wimax-util.h b/src/devices/wimax/nm-wimax-util.h new file mode 100644 index 000000000..71f5aa29d --- /dev/null +++ b/src/devices/wimax/nm-wimax-util.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef NM_WIMAX_UTIL_H +#define NM_WIMAX_UTIL_H + +#include <glib.h> + +#include <WiMaxType.h> +#include <WiMaxError.h> +#include "nm-wimax-types.h" + +void nm_wimax_util_sdk_ref (void); + +gboolean nm_wimax_util_sdk_is_initialized (void); + +void nm_wimax_util_sdk_unref (void); + +NMWimaxNspNetworkType nm_wimax_util_convert_network_type (WIMAX_API_NETWORK_TYPE wimax_network_type); + +#endif /* NM_WIMAX_UTIL_H */ diff --git a/src/devices/wwan/Makefile.am b/src/devices/wwan/Makefile.am new file mode 100644 index 000000000..988f46792 --- /dev/null +++ b/src/devices/wwan/Makefile.am @@ -0,0 +1,95 @@ +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-wwan"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(MM_GLIB_CFLAGS) + +BUILT_SOURCES = $(null) + +pkglib_LTLIBRARIES = libnm-wwan.la libnm-device-plugin-wwan.la + +########################################################### + +GLIB_GENERATED = nm-modem-enum-types.h nm-modem-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_modem_enum_types_sources = $(srcdir)/nm-modem.h + +BUILT_SOURCES += $(GLIB_GENERATED) + +libnm_wwan_la_SOURCES = \ + nm-modem-old.c \ + nm-modem-old.h \ + nm-modem-old-types.h \ + nm-modem-manager.c \ + nm-modem-manager.h \ + nm-modem.c \ + nm-modem.h \ + \ + $(GLIB_GENERATED) + +if WITH_MODEM_MANAGER_1 +libnm_wwan_la_SOURCES += \ + nm-modem-broadband.c \ + nm-modem-broadband.h +endif + +WWAN_SYMBOL_VIS_FILE=$(srcdir)/wwan-exports.ver + +libnm_wwan_la_LDFLAGS = \ + -avoid-version \ + -Wl,--version-script=$(WWAN_SYMBOL_VIS_FILE) +libnm_wwan_la_LIBADD = $(DBUS_LIBS) $(MM_GLIB_LIBS) + +########################################################### + +nm-device-modem-glue.h: $(top_srcdir)/introspection/nm-device-modem.xml + dbus-binding-tool --prefix=nm_device_modem --mode=glib-server --output=$@ $< + +BUILT_SOURCES += nm-device-modem-glue.h + +SYMBOL_VIS_FILE=$(srcdir)/exports.ver + +libnm_device_plugin_wwan_la_SOURCES = \ + nm-wwan-factory.c \ + nm-wwan-factory.h \ + nm-device-modem.c \ + nm-device-modem.h \ + nm-device-modem-glue.h + +libnm_device_plugin_wwan_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_wwan_la_LIBADD = \ + libnm-wwan.la \ + $(DBUS_LIBS) + +########################################################### + +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) $(WWAN_SYMBOL_VIS_FILE) + +if ENABLE_TESTS + +check-local: + $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wwan.so $(SYMBOL_VIS_FILE) + $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-wwan.so $(WWAN_SYMBOL_VIS_FILE) + +endif + diff --git a/src/devices/wwan/Makefile.in b/src/devices/wwan/Makefile.in new file mode 100644 index 000000000..ae33d1c87 --- /dev/null +++ b/src/devices/wwan/Makefile.in @@ -0,0 +1,886 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@WITH_MODEM_MANAGER_1_TRUE@am__append_1 = \ +@WITH_MODEM_MANAGER_1_TRUE@ nm-modem-broadband.c \ +@WITH_MODEM_MANAGER_1_TRUE@ nm-modem-broadband.h + +subdir = src/devices/wwan +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/build-aux/depcomp README +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \ + $(top_srcdir)/m4/compiler_warnings.m4 \ + $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gnome-code-coverage.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkglibdir)" +LTLIBRARIES = $(pkglib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libnm_device_plugin_wwan_la_DEPENDENCIES = libnm-wwan.la \ + $(am__DEPENDENCIES_1) +am_libnm_device_plugin_wwan_la_OBJECTS = nm-wwan-factory.lo \ + nm-device-modem.lo +libnm_device_plugin_wwan_la_OBJECTS = \ + $(am_libnm_device_plugin_wwan_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libnm_device_plugin_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libnm_device_plugin_wwan_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +libnm_wwan_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am__libnm_wwan_la_SOURCES_DIST = nm-modem-old.c nm-modem-old.h \ + nm-modem-old-types.h nm-modem-manager.c nm-modem-manager.h \ + nm-modem.c nm-modem.h nm-modem-enum-types.h \ + nm-modem-enum-types.c nm-modem-broadband.c \ + nm-modem-broadband.h +am__objects_1 = nm-modem-enum-types.lo +@WITH_MODEM_MANAGER_1_TRUE@am__objects_2 = nm-modem-broadband.lo +am_libnm_wwan_la_OBJECTS = nm-modem-old.lo nm-modem-manager.lo \ + nm-modem.lo $(am__objects_1) $(am__objects_2) +libnm_wwan_la_OBJECTS = $(am_libnm_wwan_la_OBJECTS) +libnm_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libnm_wwan_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libnm_device_plugin_wwan_la_SOURCES) \ + $(libnm_wwan_la_SOURCES) +DIST_SOURCES = $(libnm_device_plugin_wwan_la_SOURCES) \ + $(am__libnm_wwan_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CKDB_PATH = @CKDB_PATH@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@ +DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SYS_DIR = @DBUS_SYS_DIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHCLIENT_PATH = @DHCLIENT_PATH@ +DHCPCD_PATH = @DHCPCD_PATH@ +DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@ +DLLTOOL = @DLLTOOL@ +DNSMASQ_PATH = @DNSMASQ_PATH@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MAKEFILE = @GLIB_MAKEFILE@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +IPTABLES_PATH = @IPTABLES_PATH@ +IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@ +IWMX_SDK_LIBS = @IWMX_SDK_LIBS@ +KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDL = @LIBDL@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBNDP_CFLAGS = @LIBNDP_CFLAGS@ +LIBNDP_LIBS = @LIBNDP_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@ +LIBSOUP_LIBS = @LIBSOUP_LIBS@ +LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@ +LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MM_GLIB_CFLAGS = @MM_GLIB_CFLAGS@ +MM_GLIB_LIBS = @MM_GLIB_LIBS@ +MOC = @MOC@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEWT_CFLAGS = @NEWT_CFLAGS@ +NEWT_LIBS = @NEWT_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NM_MAJOR_VERSION = @NM_MAJOR_VERSION@ +NM_MICRO_VERSION = @NM_MICRO_VERSION@ +NM_MINOR_VERSION = @NM_MINOR_VERSION@ +NM_MODIFY_SYSTEM_POLICY = @NM_MODIFY_SYSTEM_POLICY@ +NM_VERSION = @NM_VERSION@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POLKIT_CFLAGS = @POLKIT_CFLAGS@ +POLKIT_LIBS = @POLKIT_LIBS@ +POSUB = @POSUB@ +PPPD_PATH = @PPPD_PATH@ +PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@ +PPPOE_PATH = @PPPOE_PATH@ +QT_CFLAGS = @QT_CFLAGS@ +QT_LIBS = @QT_LIBS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@ +SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@ +SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@ +SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +SYSTEM_CA_PATH = @SYSTEM_CA_PATH@ +UDEV_BASE_DIR = @UDEV_BASE_DIR@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND_RULES = @VALGRIND_RULES@ +VAPIGEN = @VAPIGEN@ +VAPIGEN_MAKEFILE = @VAPIGEN_MAKEFILE@ +VAPIGEN_VAPIDIR = @VAPIGEN_VAPIDIR@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nmbinary = @nmbinary@ +nmconfdir = @nmconfdir@ +nmdatadir = @nmdatadir@ +nmrundir = @nmrundir@ +nmstatedir = @nmstatedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +with_dhclient = @with_dhclient@ +with_dhcpcd = @with_dhcpcd@ +with_netconfig = @with_netconfig@ +with_resolvconf = @with_resolvconf@ +with_valgrind = @with_valgrind@ +AM_CPPFLAGS = \ + -I${top_srcdir}/src \ + -I${top_builddir}/src \ + -I${top_srcdir}/src/logging \ + -I${top_srcdir}/src/devices \ + -I${top_srcdir}/src/settings \ + -I${top_srcdir}/src/platform \ + -I${top_builddir}/include \ + -I${top_srcdir}/include \ + -I${top_builddir}/libnm-util \ + -I${top_srcdir}/libnm-util \ + -DG_LOG_DOMAIN=\""NetworkManager-wwan"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + $(DBUS_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(MM_GLIB_CFLAGS) + +BUILT_SOURCES = $(null) $(GLIB_GENERATED) nm-device-modem-glue.h +pkglib_LTLIBRARIES = libnm-wwan.la libnm-device-plugin-wwan.la + +########################################################### +GLIB_GENERATED = nm-modem-enum-types.h nm-modem-enum-types.c +GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM +GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM +nm_modem_enum_types_sources = $(srcdir)/nm-modem.h +libnm_wwan_la_SOURCES = nm-modem-old.c nm-modem-old.h \ + nm-modem-old-types.h nm-modem-manager.c nm-modem-manager.h \ + nm-modem.c nm-modem.h $(GLIB_GENERATED) $(am__append_1) +WWAN_SYMBOL_VIS_FILE = $(srcdir)/wwan-exports.ver +libnm_wwan_la_LDFLAGS = \ + -avoid-version \ + -Wl,--version-script=$(WWAN_SYMBOL_VIS_FILE) + +libnm_wwan_la_LIBADD = $(DBUS_LIBS) $(MM_GLIB_LIBS) +SYMBOL_VIS_FILE = $(srcdir)/exports.ver +libnm_device_plugin_wwan_la_SOURCES = \ + nm-wwan-factory.c \ + nm-wwan-factory.h \ + nm-device-modem.c \ + nm-device-modem.h \ + nm-device-modem-glue.h + +libnm_device_plugin_wwan_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script=$(SYMBOL_VIS_FILE) + +libnm_device_plugin_wwan_la_LIBADD = \ + libnm-wwan.la \ + $(DBUS_LIBS) + + +########################################################### +CLEANFILES = $(BUILT_SOURCES) +EXTRA_DIST = $(SYMBOL_VIS_FILE) $(WWAN_SYMBOL_VIS_FILE) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/devices/wwan/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/devices/wwan/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ + } + +uninstall-pkglibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ + done + +clean-pkglibLTLIBRARIES: + -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) + @list='$(pkglib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libnm-device-plugin-wwan.la: $(libnm_device_plugin_wwan_la_OBJECTS) $(libnm_device_plugin_wwan_la_DEPENDENCIES) $(EXTRA_libnm_device_plugin_wwan_la_DEPENDENCIES) + $(AM_V_CCLD)$(libnm_device_plugin_wwan_la_LINK) -rpath $(pkglibdir) $(libnm_device_plugin_wwan_la_OBJECTS) $(libnm_device_plugin_wwan_la_LIBADD) $(LIBS) + +libnm-wwan.la: $(libnm_wwan_la_OBJECTS) $(libnm_wwan_la_DEPENDENCIES) $(EXTRA_libnm_wwan_la_DEPENDENCIES) + $(AM_V_CCLD)$(libnm_wwan_la_LINK) -rpath $(pkglibdir) $(libnm_wwan_la_OBJECTS) $(libnm_wwan_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-device-modem.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-broadband.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-enum-types.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-old.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wwan-factory.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +@ENABLE_TESTS_FALSE@check-local: +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(pkglibdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-pkglibLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkglibLTLIBRARIES + +.MAKE: all check check-am install install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am check-local clean \ + clean-generic clean-libtool clean-pkglibLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkglibLTLIBRARIES install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkglibLTLIBRARIES + +include $(GLIB_MAKEFILE) + +@GNOME_CODE_COVERAGE_RULES@ + +########################################################### + +nm-device-modem-glue.h: $(top_srcdir)/introspection/nm-device-modem.xml + dbus-binding-tool --prefix=nm_device_modem --mode=glib-server --output=$@ $< + +@ENABLE_TESTS_TRUE@check-local: +@ENABLE_TESTS_TRUE@ $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wwan.so $(SYMBOL_VIS_FILE) +@ENABLE_TESTS_TRUE@ $(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-wwan.so $(WWAN_SYMBOL_VIS_FILE) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/devices/wwan/README b/src/devices/wwan/README new file mode 100644 index 000000000..4661c0428 --- /dev/null +++ b/src/devices/wwan/README @@ -0,0 +1,45 @@ + +ModemManager integration is organized as follows: + + +Common source +******************************************************************************** + + * nm-modem.[h|c]: + Defines the basic `NMModem' object. The core NetworkManager implementation + will use this interface exclusively, regardless of the real final type of + the modem object. + + * nm-modem-manager.[h|c]: + Defines the `NMModemManager' object, which takes care of listening to + signals from the DBus interface notifying about added or removed modems. + It also takes care of creating proper `NMModem' objects from the + information retrieved from the DBus interface. + + +ModemManager 0.7 integration +******************************************************************************** + + * nm-modem-broadband.[h|c]: + Defines the `NMModemBroadband' object, which is a subclass of `NMModem'. + This object handles both 3GPP and 3GPP2 modems exposed in the new + `ModemManager1' interface. + + +ModemManager 0.4/0.5/0.6 integration +******************************************************************************** + + * nm-modem-old-types.h: + Defines helper types to use with the (old) ModemManager DBus API. + + * nm-modem-old.[h|c]: + Defines the `NMModemGeneric' object. All modem objects based on the old + ModemManager interface are subclasses of this one. + + * nm-modem-gsm.[h|c]: + Defines the `NMModemGsm' object, which is a subclass of `NMModemGeneric'. + This object handles 3GPP-specific (GSM, UMTS, HSPA, LTE) modems. + + * nm-modem-cdma.[h|c]: + Defines the `NMModemCdma' object, which is a subclass of `NMModemGeneric'. + This object handles 3GPP2-specific modems (CDMA, EV-DO). diff --git a/src/devices/wwan/exports.ver b/src/devices/wwan/exports.ver new file mode 100644 index 000000000..d2c451244 --- /dev/null +++ b/src/devices/wwan/exports.ver @@ -0,0 +1,7 @@ +{ +global: + nm_device_factory_create; + nm_device_factory_get_device_type; +local: + *; +}; diff --git a/src/devices/wwan/nm-device-modem-glue.h b/src/devices/wwan/nm-device-modem-glue.h new file mode 100644 index 000000000..21271491e --- /dev/null +++ b/src/devices/wwan/nm-device-modem-glue.h @@ -0,0 +1,73 @@ +/* Generated by dbus-binding-tool; do not edit! */ + + +#ifndef __dbus_glib_marshal_nm_device_modem_MARSHAL_H__ +#define __dbus_glib_marshal_nm_device_modem_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +G_END_DECLS + +#endif /* __dbus_glib_marshal_nm_device_modem_MARSHAL_H__ */ + +#include <dbus/dbus-glib.h> +static const DBusGMethodInfo dbus_glib_nm_device_modem_methods[] = { +}; + +const DBusGObjectInfo dbus_glib_nm_device_modem_object_info = { 1, + dbus_glib_nm_device_modem_methods, + 0, +"\0", +"org.freedesktop.NetworkManager.Device.Modem\0PropertiesChanged\0\0", +"org.freedesktop.NetworkManager.Device.Modem\0ModemCapabilities\0modem_capabilities\0read\0org.freedesktop.NetworkManager.Device.Modem\0CurrentCapabilities\0current_capabilities\0read\0\0" +}; + diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c new file mode 100644 index 000000000..678f77950 --- /dev/null +++ b/src/devices/wwan/nm-device-modem.c @@ -0,0 +1,646 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2011 Red Hat, Inc. + */ + +#include "config.h" + +#include <glib.h> + +#include "nm-device-modem.h" +#include "nm-modem.h" +#include "nm-modem-old.h" +#include "nm-device-private.h" +#include "nm-rfkill-manager.h" +#include "nm-logging.h" +#include "nm-dbus-manager.h" +#include "nm-settings-connection.h" + +#if WITH_MODEM_MANAGER_1 +#include "nm-modem-broadband.h" +#endif + +G_DEFINE_TYPE (NMDeviceModem, nm_device_modem, NM_TYPE_DEVICE) + +#define NM_DEVICE_MODEM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_MODEM, NMDeviceModemPrivate)) + +#include "nm-device-modem-glue.h" + +typedef struct { + NMModem *modem; + NMDeviceModemCapabilities caps; + NMDeviceModemCapabilities current_caps; + gboolean rf_enabled; +} NMDeviceModemPrivate; + +enum { + PROP_0, + PROP_MODEM, + PROP_CAPABILITIES, + PROP_CURRENT_CAPABILITIES, +}; + +/*****************************************************************************/ + +static void +ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + switch (nm_device_get_state (device)) { + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + break; + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + case NM_DEVICE_STATE_ACTIVATED: + if (nm_device_activate_ip4_state_in_conf (device)) + nm_device_activate_schedule_ip4_config_timeout (device); + else { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + break; + default: + break; + } +} + +static void +modem_prepare_result (NMModem *modem, + gboolean success, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDeviceState state; + + state = nm_device_get_state (device); + g_return_if_fail (state == NM_DEVICE_STATE_PREPARE); + + if (success) + nm_device_activate_schedule_stage2_device_config (device); + else { + if (reason == NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT) { + /* If the connect failed because the SIM PIN was wrong don't allow + * the device to be auto-activated anymore, which would risk locking + * the SIM if the incorrect PIN continues to be used. + */ + g_object_set (G_OBJECT (device), NM_DEVICE_AUTOCONNECT, FALSE, NULL); + nm_log_info (LOGD_MB, "(%s): disabling autoconnect due to failed SIM PIN", + nm_device_get_iface (device)); + } + + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + } +} + +static void +modem_auth_requested (NMModem *modem, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + /* Auth requests (PIN, PAP/CHAP passwords, etc) only get handled + * during activation. + */ + if (!nm_device_is_activating (device)) + return; + + nm_device_state_changed (device, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_NONE); +} + +static void +modem_auth_result (NMModem *modem, GError *error, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + if (error) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } else { + /* Otherwise, on success for modem secrets we need to schedule stage1 again */ + g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH); + nm_device_activate_schedule_stage1_device_prepare (device); + } +} + +static void +modem_ip4_config_result (NMModem *self, + NMIP4Config *config, + GError *error, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + + g_return_if_fail (nm_device_activate_ip4_state_in_conf (device) == TRUE); + + if (error) { + nm_log_warn (LOGD_MB | LOGD_IP4, "retrieving IP4 configuration failed: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else + nm_device_activate_schedule_ip4_config_result (device, config); +} + +static void +data_port_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + + /* We set the IP iface in the device as soon as we know it, so that we + * properly ifup it if needed */ + nm_device_set_ip_iface (self, nm_modem_get_data_port (modem)); +} + +static void +modem_state_cb (NMModem *modem, + NMModemState new_state, + NMModemState old_state, + gpointer user_data) +{ + NMDevice *device = NM_DEVICE (user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device); + NMDeviceState dev_state = nm_device_get_state (device); + + if (new_state <= NM_MODEM_STATE_DISABLING && + old_state > NM_MODEM_STATE_DISABLING && + priv->rf_enabled) { + /* Called when the ModemManager modem enabled state is changed externally + * to NetworkManager (eg something using MM's D-Bus API directly). + */ + if (nm_device_is_activating (device) || dev_state == NM_DEVICE_STATE_ACTIVATED) { + /* user-initiated action, hence DISCONNECTED not FAILED */ + nm_device_state_changed (device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + return; + } + } + + if (new_state < NM_MODEM_STATE_CONNECTING && + old_state >= NM_MODEM_STATE_CONNECTING && + dev_state >= NM_DEVICE_STATE_NEED_AUTH && + dev_state <= NM_DEVICE_STATE_ACTIVATED) { + /* Fail the device if the modem disconnects unexpectedly while the + * device is activating/activated. */ + nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return; + } + + if (new_state > NM_MODEM_STATE_LOCKED && old_state == NM_MODEM_STATE_LOCKED) { + /* If the modem is now unlocked, enable/disable it according to the + * device's enabled/disabled state. + */ + nm_modem_set_mm_enabled (priv->modem, priv->rf_enabled); + } + + if ((dev_state >= NM_DEVICE_STATE_DISCONNECTED) && !nm_device_is_available (device)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_MODEM_FAILED); + return; + } + + if ((dev_state == NM_DEVICE_STATE_UNAVAILABLE) && nm_device_is_available (device)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_MODEM_AVAILABLE); + return; + } +} + +static void +modem_removed_cb (NMModem *modem, gpointer user_data) +{ + g_signal_emit_by_name (NM_DEVICE (user_data), NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +static gboolean +owns_iface (NMDevice *device, const char *iface) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device); + + g_assert (priv->modem); + return nm_modem_owns_port (priv->modem, iface); +} + +/*****************************************************************************/ + +static void +device_state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device); + NMConnection *connection = nm_device_get_connection (device); + + g_assert (priv->modem); + + if (new_state == NM_DEVICE_STATE_UNAVAILABLE && + old_state < NM_DEVICE_STATE_UNAVAILABLE) { + /* Log initial modem state */ + nm_log_info (LOGD_MB, "(%s): modem state '%s'", + nm_device_get_iface (device), + nm_modem_state_to_string (nm_modem_get_state (priv->modem))); + } + + nm_modem_device_state_changed (priv->modem, new_state, old_state, reason); + + switch (reason) { + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED: + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING: + case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED: + case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: + case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: + case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG: + case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: + case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + case NM_DEVICE_STATE_REASON_GSM_APN_FAILED: + /* Block autoconnect of the just-failed connection for situations + * where a retry attempt would just fail again. + */ + if (connection) + nm_settings_connection_set_autoconnect_blocked_reason (NM_SETTINGS_CONNECTION (connection), reason); + break; + default: + break; + } +} + +static guint +get_hw_address_length (NMDevice *device, gboolean *out_permanent) +{ + return 0; +} + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + if (!NM_DEVICE_CLASS (nm_device_modem_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + return nm_modem_check_connection_compatible (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, connection); +} + +static gboolean +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) +{ + NMDeviceModem *self = NM_DEVICE_MODEM (device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self); + NMModemState state; + + if (!priv->rf_enabled || !priv->modem) + return FALSE; + + state = nm_modem_get_state (priv->modem); + if (state <= NM_MODEM_STATE_INITIALIZING) + return FALSE; + + if (state == NM_MODEM_STATE_LOCKED) { + NMSettingGsm *s_gsm = nm_connection_get_setting_gsm (connection); + + /* Can't use a connection without a PIN if the modem is locked */ + if (!s_gsm || !nm_setting_gsm_get_pin (s_gsm)) + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection (NMDevice *device, + NMConnection *connection, + const char *specific_object, + const GSList *existing_connections, + GError **error) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device); + + return nm_modem_complete_connection (priv->modem, connection, existing_connections, error); +} + +static void +deactivate (NMDevice *device) +{ + nm_modem_deactivate (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, device); +} + +static NMActStageReturn +act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason) +{ + NMActStageReturn ret; + NMActRequest *req; + + ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage1_prepare (device, reason); + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) + return ret; + + req = nm_device_get_act_request (device); + g_assert (req); + + return nm_modem_act_stage1_prepare (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, req, reason); +} + +static NMActStageReturn +act_stage2_config (NMDevice *device, NMDeviceStateReason *reason) +{ + NMActRequest *req; + + req = nm_device_get_act_request (device); + g_assert (req); + + return nm_modem_act_stage2_config (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, req, reason); +} + +static NMActStageReturn +act_stage3_ip4_config_start (NMDevice *device, + NMIP4Config **out_config, + NMDeviceStateReason *reason) +{ + return nm_modem_stage3_ip4_config_start (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, + device, + NM_DEVICE_CLASS (nm_device_modem_parent_class), + reason); +} + +static void +ip4_config_pre_commit (NMDevice *device, NMIP4Config *config) +{ + nm_modem_ip4_pre_commit (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, device, config); +} + +static NMActStageReturn +act_stage3_ip6_config_start (NMDevice *device, + NMIP6Config **out_config, + NMDeviceStateReason *reason) +{ + return nm_modem_stage3_ip6_config_start (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, + device, + NM_DEVICE_CLASS (nm_device_modem_parent_class), + reason); +} + +/*****************************************************************************/ + +static gboolean +get_enabled (NMDevice *device) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device); + NMModemState modem_state = nm_modem_get_state (priv->modem); + + return priv->rf_enabled && (modem_state >= NM_MODEM_STATE_LOCKED); +} + +static void +set_enabled (NMDevice *device, gboolean enabled) +{ + NMDeviceModem *self = NM_DEVICE_MODEM (device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self); + + /* Called only by the Manager in response to rfkill switch changes or + * global user WWAN enable/disable preference changes. + */ + priv->rf_enabled = enabled; + + if (priv->modem) { + /* Sync the ModemManager modem enabled/disabled with rfkill/user preference */ + nm_modem_set_mm_enabled (priv->modem, enabled); + } + + if (enabled == FALSE) { + nm_device_state_changed (device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + } +} + +static gboolean +is_available (NMDevice *dev) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (dev); + NMModemState modem_state; + + if (!priv->rf_enabled) { + nm_log_dbg (LOGD_MB, "(%s): not available because WWAN airplane mode is on", + nm_device_get_iface (dev)); + return FALSE; + } + + g_assert (priv->modem); + modem_state = nm_modem_get_state (priv->modem); + if (modem_state <= NM_MODEM_STATE_INITIALIZING) { + nm_log_dbg (LOGD_MB, "(%s): not available because modem is not ready (%s)", + nm_device_get_iface (dev), + nm_modem_state_to_string (modem_state)); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +NMDevice * +nm_device_modem_new (NMModem *modem) +{ + NMDeviceModemCapabilities caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + NMDeviceModemCapabilities current_caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + NMDevice *device; + const char *data_port; + + g_return_val_if_fail (NM_IS_MODEM (modem), NULL); + + /* Load capabilities */ + nm_modem_get_capabilities (modem, &caps, ¤t_caps); + + device = (NMDevice *) g_object_new (NM_TYPE_DEVICE_MODEM, + NM_DEVICE_UDI, nm_modem_get_path (modem), + NM_DEVICE_IFACE, nm_modem_get_uid (modem), + NM_DEVICE_DRIVER, nm_modem_get_driver (modem), + NM_DEVICE_TYPE_DESC, "Broadband", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_MODEM, + NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WWAN, + NM_DEVICE_MODEM_MODEM, modem, + NM_DEVICE_MODEM_CAPABILITIES, caps, + NM_DEVICE_MODEM_CURRENT_CAPABILITIES, current_caps, + NULL); + + /* If the data port is known, set it as the IP interface immediately */ + data_port = nm_modem_get_data_port (modem); + if (data_port) + nm_device_set_ip_iface (device, data_port); + + return device; +} + +static void +nm_device_modem_init (NMDeviceModem *self) +{ +} + +static void +set_modem (NMDeviceModem *self, NMModem *modem) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self); + + g_return_if_fail (modem != NULL); + + priv->modem = g_object_ref (modem); + + g_signal_connect (modem, NM_MODEM_PPP_FAILED, G_CALLBACK (ppp_failed), self); + g_signal_connect (modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK (modem_prepare_result), self); + g_signal_connect (modem, NM_MODEM_IP4_CONFIG_RESULT, G_CALLBACK (modem_ip4_config_result), self); + g_signal_connect (modem, NM_MODEM_AUTH_REQUESTED, G_CALLBACK (modem_auth_requested), self); + g_signal_connect (modem, NM_MODEM_AUTH_RESULT, G_CALLBACK (modem_auth_result), self); + g_signal_connect (modem, NM_MODEM_STATE_CHANGED, G_CALLBACK (modem_state_cb), self); + g_signal_connect (modem, NM_MODEM_REMOVED, G_CALLBACK (modem_removed_cb), self); + + /* In the old ModemManager the data port is known from the very beginning; + * while in the new ModemManager the data port is set afterwards when the bearer gets + * created */ + g_signal_connect (modem, "notify::" NM_MODEM_DATA_PORT, G_CALLBACK (data_port_changed_cb), self); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_MODEM: + /* construct-only */ + set_modem (NM_DEVICE_MODEM (object), g_value_get_object (value)); + break; + case PROP_CAPABILITIES: + priv->caps = g_value_get_uint (value); + break; + case PROP_CURRENT_CAPABILITIES: + priv->current_caps = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object (value, priv->modem); + break; + case PROP_CAPABILITIES: + g_value_set_uint (value, priv->caps); + break; + case PROP_CURRENT_CAPABILITIES: + g_value_set_uint (value, priv->current_caps); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object); + + if (priv->modem) + g_signal_handlers_disconnect_by_data (priv->modem, NM_DEVICE_MODEM (object)); + g_clear_object (&priv->modem); + + G_OBJECT_CLASS (nm_device_modem_parent_class)->dispose (object); +} + +static void +nm_device_modem_class_init (NMDeviceModemClass *mclass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (mclass); + NMDeviceClass *device_class = NM_DEVICE_CLASS (mclass); + + g_type_class_add_private (object_class, sizeof (NMDeviceModemPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + device_class->get_hw_address_length = get_hw_address_length; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->deactivate = deactivate; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start; + device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start; + device_class->ip4_config_pre_commit = ip4_config_pre_commit; + device_class->get_enabled = get_enabled; + device_class->set_enabled = set_enabled; + device_class->owns_iface = owns_iface; + device_class->is_available = is_available; + + device_class->state_changed = device_state_changed; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_MODEM, + g_param_spec_object (NM_DEVICE_MODEM_MODEM, + "Modem", + "Modem", + NM_TYPE_MODEM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CAPABILITIES, + g_param_spec_uint (NM_DEVICE_MODEM_CAPABILITIES, + "Modem Capabilities", + "Modem Capabilities", + 0, G_MAXUINT32, NM_DEVICE_MODEM_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CURRENT_CAPABILITIES, + g_param_spec_uint (NM_DEVICE_MODEM_CURRENT_CAPABILITIES, + "Current modem Capabilities", + "Current modem Capabilities", + 0, G_MAXUINT32, NM_DEVICE_MODEM_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), + G_TYPE_FROM_CLASS (mclass), + &dbus_glib_nm_device_modem_object_info); +} diff --git a/src/devices/wwan/nm-device-modem.h b/src/devices/wwan/nm-device-modem.h new file mode 100644 index 000000000..ee6b2988c --- /dev/null +++ b/src/devices/wwan/nm-device-modem.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef NM_DEVICE_MODEM_H +#define NM_DEVICE_MODEM_H + +#include <glib.h> +#include <glib-object.h> + +#include "nm-device.h" +#include "nm-modem.h" + +#define NM_TYPE_DEVICE_MODEM (nm_device_modem_get_type ()) +#define NM_DEVICE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModem)) +#define NM_DEVICE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_MODEM, NMDeviceModemClass)) +#define NM_IS_DEVICE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_MODEM)) +#define NM_IS_DEVICE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_MODEM)) +#define NM_DEVICE_MODEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModemClass)) + +#define NM_DEVICE_MODEM_MODEM "modem" +#define NM_DEVICE_MODEM_CAPABILITIES "modem-capabilities" +#define NM_DEVICE_MODEM_CURRENT_CAPABILITIES "current-capabilities" + +typedef struct { + NMDevice parent; +} NMDeviceModem; + +typedef struct { + NMDeviceClass parent; + +} NMDeviceModemClass; + +GType nm_device_modem_get_type (void); + +NMDevice *nm_device_modem_new (NMModem *modem); + +#endif /* NM_DEVICE_MODEM_H */ diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c new file mode 100644 index 000000000..807b89243 --- /dev/null +++ b/src/devices/wwan/nm-modem-broadband.c @@ -0,0 +1,987 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <glib/gi18n.h> +#include <string.h> +#include <libmm-glib.h> +#include "nm-modem-broadband.h" +#include "nm-setting-connection.h" +#include "nm-logging.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" + +G_DEFINE_TYPE (NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM) + +struct _NMModemBroadbandPrivate { + /* The modem object from dbus */ + MMObject *modem_object; + /* Per-interface objects */ + MMModem *modem_iface; + MMModemSimple *simple_iface; + + /* Connection setup */ + MMSimpleConnectProperties *connect_properties; + MMBearer *bearer; + MMBearerIpConfig *ipv4_config; + MMBearerIpConfig *ipv6_config; + + guint32 pin_tries; +}; + +enum { + PROP_0, + PROP_MODEM, +}; + +#define MODEM_CAPS_3GPP(caps) (caps & (MM_MODEM_CAPABILITY_GSM_UMTS | \ + MM_MODEM_CAPABILITY_LTE | \ + MM_MODEM_CAPABILITY_LTE_ADVANCED)) + +#define MODEM_CAPS_3GPP2(caps) (caps & (MM_MODEM_CAPABILITY_CDMA_EVDO)) + +/* Maximum time to keep the DBus call waiting for a connection result */ +#define MODEM_CONNECT_TIMEOUT_SECS 120 + +/*****************************************************************************/ + +static NMDeviceStateReason +translate_mm_error (GError *error) +{ + NMDeviceStateReason reason; + + g_return_val_if_fail (error != NULL, NM_DEVICE_STATE_REASON_UNKNOWN); + + if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_CARRIER)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER; + else if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_DIALTONE)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE; + else if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_BUSY)) + reason = NM_DEVICE_STATE_REASON_MODEM_BUSY; + else if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_ANSWER)) + reason = NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_WRONG; + else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD)) + reason = NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT; + else { + /* unable to map the ModemManager error to a NM_DEVICE_STATE_REASON */ + nm_log_dbg (LOGD_MB, "unmapped error detected: '%s'", error->message); + reason = NM_DEVICE_STATE_REASON_UNKNOWN; + } + + return reason; +} + +/*****************************************************************************/ + +static void +get_capabilities (NMModem *_self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + MMModemCapability all_supported = MM_MODEM_CAPABILITY_NONE; + MMModemCapability *supported; + guint n_supported; + + /* For now, we don't care about the capability combinations, just merge all + * combinations in a single mask */ + if (mm_modem_get_supported_capabilities (self->priv->modem_iface, &supported, &n_supported)) { + guint i; + + for (i = 0; i < n_supported; i++) + all_supported |= supported[i]; + + g_free (supported); + } + + *modem_caps = (NMDeviceModemCapabilities) all_supported; + *current_caps = (NMDeviceModemCapabilities) mm_modem_get_current_capabilities (self->priv->modem_iface); +} + +static gboolean +owns_port (NMModem *_self, const char *iface) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + const MMModemPortInfo *ports = NULL; + guint n_ports = 0, i; + gboolean owns = FALSE; + + mm_modem_peek_ports (self->priv->modem_iface, &ports, &n_ports); + for (i = 0; i < n_ports && !owns; i++) + owns = (g_strcmp0 (iface, ports[i].name) == 0); + return owns; +} + +/*****************************************************************************/ + +static void +ask_for_pin (NMModemBroadband *self) +{ + guint32 tries; + + tries = self->priv->pin_tries++; + nm_modem_get_secrets (NM_MODEM (self), + NM_SETTING_GSM_SETTING_NAME, + tries ? TRUE : FALSE, + NM_SETTING_GSM_PIN); +} + +static void +connect_ready (MMModemSimple *simple_iface, + GAsyncResult *res, + NMModemBroadband *self) +{ + GError *error = NULL; + guint ip_method; + + g_clear_object (&self->priv->connect_properties); + + self->priv->bearer = mm_modem_simple_connect_finish (simple_iface, res, &error); + if (!self->priv->bearer) { + if (g_error_matches (error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) || + (g_error_matches (error, + MM_CORE_ERROR, + MM_CORE_ERROR_UNAUTHORIZED) && + mm_modem_get_unlock_required (self->priv->modem_iface) == MM_MODEM_LOCK_SIM_PIN)) { + /* Request PIN */ + ask_for_pin (self); + } else { + /* Strip remote error info before logging it */ + if (g_dbus_error_is_remote_error (error)) + g_dbus_error_strip_remote_error (error); + + nm_log_warn (LOGD_MB, "(%s) failed to connect modem: %s", + nm_modem_get_uid (NM_MODEM (self)), + error && error->message ? error->message : "(unknown)"); + g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (error)); + } + + g_clear_error (&error); + g_object_unref (self); + return; + } + + /* Grab IP configurations */ + self->priv->ipv4_config = mm_bearer_get_ipv4_config (self->priv->bearer); + self->priv->ipv6_config = mm_bearer_get_ipv6_config (self->priv->bearer); + + switch (mm_bearer_ip_config_get_method (self->priv->ipv4_config)) { + case MM_BEARER_IP_METHOD_PPP: + ip_method = MM_MODEM_IP_METHOD_PPP; + break; + case MM_BEARER_IP_METHOD_STATIC: + ip_method = MM_MODEM_IP_METHOD_STATIC; + break; + case MM_BEARER_IP_METHOD_DHCP: + ip_method = MM_MODEM_IP_METHOD_DHCP; + break; + default: + error = g_error_new (NM_MODEM_ERROR, + NM_MODEM_ERROR_CONNECTION_INVALID, + "invalid IP config"); + nm_log_warn (LOGD_MB, "(%s) failed to connect modem: %s", + nm_modem_get_uid (NM_MODEM (self)), + error->message); + g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (error)); + g_error_free (error); + g_object_unref (self); + return; + } + + /* IPv4 for now only */ + g_object_set (self, + NM_MODEM_DATA_PORT, mm_bearer_get_interface (self->priv->bearer), + NM_MODEM_IP_METHOD, ip_method, + NM_MODEM_IP_TIMEOUT, mm_bearer_get_ip_timeout (self->priv->bearer), + NULL); + + g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, TRUE, NM_DEVICE_STATE_REASON_NONE); + g_object_unref (self); +} + +static MMSimpleConnectProperties * +create_cdma_connect_properties (NMConnection *connection) +{ + NMSettingCdma *setting; + MMSimpleConnectProperties *properties; + const gchar *str; + + setting = nm_connection_get_setting_cdma (connection); + properties = mm_simple_connect_properties_new (); + + str = nm_setting_cdma_get_number (setting); + if (str) + mm_simple_connect_properties_set_number (properties, str); + + return properties; +} + +static MMSimpleConnectProperties * +create_gsm_connect_properties (NMConnection *connection) +{ + NMSettingGsm *setting; + NMSettingPPP *s_ppp; + MMSimpleConnectProperties *properties; + const gchar *str; + + setting = nm_connection_get_setting_gsm (connection); + properties = mm_simple_connect_properties_new (); + + /* TODO: not needed */ + str = nm_setting_gsm_get_number (setting); + if (str) + mm_simple_connect_properties_set_number (properties, str); + + str = nm_setting_gsm_get_apn (setting); + if (str) + mm_simple_connect_properties_set_apn (properties, str); + + str = nm_setting_gsm_get_network_id (setting); + if (str) + mm_simple_connect_properties_set_operator_id (properties, str); + + str = nm_setting_gsm_get_pin (setting); + if (str) + mm_simple_connect_properties_set_pin (properties, str); + + str = nm_setting_gsm_get_username (setting); + if (str) + mm_simple_connect_properties_set_user (properties, str); + + str = nm_setting_gsm_get_password (setting); + if (str) + mm_simple_connect_properties_set_password (properties, str); + + /* Roaming */ + if (nm_setting_gsm_get_home_only (setting)) + mm_simple_connect_properties_set_allow_roaming (properties, FALSE); + + /* For IpMethod == STATIC or DHCP */ + s_ppp = nm_connection_get_setting_ppp (connection); + if (s_ppp) { + MMBearerAllowedAuth allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + + if (nm_setting_ppp_get_noauth (s_ppp)) + allowed_auth = MM_BEARER_ALLOWED_AUTH_NONE; + if (!nm_setting_ppp_get_refuse_pap (s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_PAP; + if (!nm_setting_ppp_get_refuse_chap (s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_CHAP; + if (!nm_setting_ppp_get_refuse_mschap (s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAP; + if (!nm_setting_ppp_get_refuse_mschapv2 (s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAPV2; + if (!nm_setting_ppp_get_refuse_eap (s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_EAP; + + mm_simple_connect_properties_set_allowed_auth (properties, allowed_auth); + } + + return properties; +} + +static NMActStageReturn +act_stage1_prepare (NMModem *_self, + NMConnection *connection, + NMDeviceStateReason *reason) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + MMModemCapability caps; + + g_clear_object (&self->priv->connect_properties); + + caps = mm_modem_get_current_capabilities (self->priv->modem_iface); + if (MODEM_CAPS_3GPP (caps)) + self->priv->connect_properties = create_gsm_connect_properties (connection); + else if (MODEM_CAPS_3GPP2 (caps)) + self->priv->connect_properties = create_cdma_connect_properties (connection); + else { + nm_log_warn (LOGD_MB, "(%s) not a mobile broadband modem", + nm_modem_get_uid (NM_MODEM (self))); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (!self->priv->simple_iface) + self->priv->simple_iface = mm_object_get_modem_simple (self->priv->modem_object); + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (self->priv->simple_iface), MODEM_CONNECT_TIMEOUT_SECS * 1000); + mm_modem_simple_connect (self->priv->simple_iface, + self->priv->connect_properties, + NULL, + (GAsyncReadyCallback)connect_ready, + g_object_ref (self)); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static gboolean +check_connection_compatible (NMModem *_self, NMConnection *connection) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + MMModemCapability modem_caps; + NMSettingConnection *s_con; + + modem_caps = mm_modem_get_current_capabilities (self->priv->modem_iface); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + if (MODEM_CAPS_3GPP (modem_caps)) { + NMSettingGsm *s_gsm; + + if (!g_str_equal (nm_setting_connection_get_connection_type (s_con), + NM_SETTING_GSM_SETTING_NAME)) + return FALSE; + + s_gsm = nm_connection_get_setting_gsm (connection); + if (!s_gsm) + return FALSE; + + return TRUE; + } + + if (MODEM_CAPS_3GPP2 (modem_caps)) { + NMSettingCdma *s_cdma; + + if (!g_str_equal (nm_setting_connection_get_connection_type (s_con), + NM_SETTING_CDMA_SETTING_NAME)) + return FALSE; + + s_cdma = nm_connection_get_setting_cdma (connection); + if (!s_cdma) + return FALSE; + + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +complete_connection (NMModem *_self, + NMConnection *connection, + const GSList *existing_connections, + GError **error) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + MMModemCapability modem_caps; + NMSettingPPP *s_ppp; + + modem_caps = mm_modem_get_current_capabilities (self->priv->modem_iface); + + /* PPP settings common to 3GPP and 3GPP2 */ + s_ppp = nm_connection_get_setting_ppp (connection); + if (!s_ppp) { + s_ppp = (NMSettingPPP *) nm_setting_ppp_new (); + g_object_set (G_OBJECT (s_ppp), + NM_SETTING_PPP_LCP_ECHO_FAILURE, 5, + NM_SETTING_PPP_LCP_ECHO_INTERVAL, 30, + NULL); + nm_connection_add_setting (connection, NM_SETTING (s_ppp)); + } + + if (MODEM_CAPS_3GPP (modem_caps)) { + NMSettingGsm *s_gsm; + + s_gsm = nm_connection_get_setting_gsm (connection); + if (!s_gsm || !nm_setting_gsm_get_apn (s_gsm)) { + /* Need an APN at least */ + g_set_error_literal (error, + NM_SETTING_GSM_ERROR, + NM_SETTING_GSM_ERROR_MISSING_PROPERTY, + NM_SETTING_GSM_APN); + return FALSE; + } + + /* TODO: This is not needed */ + if (!nm_setting_gsm_get_number (s_gsm)) + g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL); + + nm_utils_complete_generic (connection, + NM_SETTING_GSM_SETTING_NAME, + existing_connections, + _("GSM connection %d"), + NULL, + FALSE); /* No IPv6 yet by default */ + + return TRUE; + } + + if (MODEM_CAPS_3GPP2 (modem_caps)) { + NMSettingCdma *s_cdma; + + s_cdma = nm_connection_get_setting_cdma (connection); + if (!s_cdma) { + s_cdma = (NMSettingCdma *) nm_setting_cdma_new (); + nm_connection_add_setting (connection, NM_SETTING (s_cdma)); + } + + if (!nm_setting_cdma_get_number (s_cdma)) + g_object_set (G_OBJECT (s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL); + + nm_utils_complete_generic (connection, + NM_SETTING_CDMA_SETTING_NAME, + existing_connections, + _("CDMA connection %d"), + NULL, + FALSE); /* No IPv6 yet by default */ + + return TRUE; + } + + g_set_error (error, + NM_MODEM_ERROR, + NM_MODEM_ERROR_CONNECTION_INCOMPATIBLE, + "Device is not a mobile broadband modem"); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +get_user_pass (NMModem *modem, + NMConnection *connection, + const char **user, + const char **pass) +{ + NMSettingGsm *s_gsm; + NMSettingCdma *s_cdma; + + s_gsm = nm_connection_get_setting_gsm (connection); + s_cdma = nm_connection_get_setting_cdma (connection); + if (!s_gsm && !s_cdma) + return FALSE; + + if (user) { + if (s_gsm) + *user = nm_setting_gsm_get_username (s_gsm); + else if (s_cdma) + *user = nm_setting_cdma_get_username (s_cdma); + } + if (pass) { + if (s_gsm) + *pass = nm_setting_gsm_get_password (s_gsm); + else if (s_cdma) + *pass = nm_setting_cdma_get_password (s_cdma); + } + + return TRUE; +} + +/*****************************************************************************/ +/* Query/Update enabled state */ + +static void +set_power_state_low_ready (MMModem *modem, + GAsyncResult *result, + NMModemBroadband *self) +{ + GError *error = NULL; + + if (!mm_modem_set_power_state_finish (modem, result, &error)) { + /* Log but ignore errors; not all modems support low power state */ + nm_log_dbg (LOGD_MB, "(%s) failed to set modem low power state: %s", + nm_modem_get_uid (NM_MODEM (self)), + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + } + + /* Balance refcount */ + g_object_unref (self); +} + +static void +modem_disable_ready (MMModem *modem_iface, + GAsyncResult *res, + NMModemBroadband *self) +{ + GError *error = NULL; + + if (mm_modem_disable_finish (modem_iface, res, &error)) { + /* Once disabled, move to low-power mode */ + mm_modem_set_power_state (modem_iface, + MM_MODEM_POWER_STATE_LOW, + NULL, + (GAsyncReadyCallback) set_power_state_low_ready, + g_object_ref (self)); + } else { + nm_log_warn (LOGD_MB, "(%s) failed to disable modem: %s", + nm_modem_get_uid (NM_MODEM (self)), + error && error->message ? error->message : "(unknown)"); + nm_modem_set_prev_state (NM_MODEM (self), "disable failed"); + g_clear_error (&error); + } + + /* Balance refcount */ + g_object_unref (self); +} + +static void +modem_enable_ready (MMModem *modem_iface, + GAsyncResult *res, + NMModemBroadband *self) +{ + GError *error = NULL; + + if (!mm_modem_enable_finish (modem_iface, res, &error)) { + nm_log_warn (LOGD_MB, "(%s) failed to enable modem: %s", + nm_modem_get_uid (NM_MODEM (self)), + error && error->message ? error->message : "(unknown)"); + nm_modem_set_prev_state (NM_MODEM (self), "enable failed"); + g_clear_error (&error); + } + + /* Balance refcount */ + g_object_unref (self); +} + +static void +set_mm_enabled (NMModem *_self, + gboolean enabled) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + + if (enabled) { + mm_modem_enable (self->priv->modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback)modem_enable_ready, + g_object_ref (self)); + } else { + mm_modem_disable (self->priv->modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback)modem_disable_ready, + g_object_ref (self)); + } +} + +/*****************************************************************************/ +/* IP method static */ + +static gboolean +ip_string_to_network_address (const gchar *str, + guint32 *out) +{ + guint32 addr = 0; + gboolean success = FALSE; + + if (!str || inet_pton (AF_INET, str, &addr) != 1) + addr = 0; + else + success = TRUE; + + *out = (guint32)addr; + return success; +} + +static gboolean +static_stage3_done (NMModemBroadband *self) +{ + GError *error = NULL; + NMIP4Config *config = NULL; + const gchar *address_string; + const gchar *gw_string; + guint32 address_network; + guint32 gw; + NMPlatformIP4Address address; + const gchar **dns; + guint i; + + g_assert (self->priv->ipv4_config); + + nm_log_info (LOGD_MB, "(%s): IPv4 static configuration:", + nm_modem_get_uid (NM_MODEM (self))); + + /* Fully fail if invalid IP address retrieved */ + address_string = mm_bearer_ip_config_get_address (self->priv->ipv4_config); + if (!ip_string_to_network_address (address_string, &address_network)) { + error = g_error_new (NM_MODEM_ERROR, + NM_MODEM_ERROR_CONNECTION_INVALID, + "(%s) retrieving IP4 configuration failed: invalid address given '%s'", + nm_modem_get_uid (NM_MODEM (self)), + address_string); + goto out; + } + + /* Missing gateway not a hard failure */ + gw_string = mm_bearer_ip_config_get_gateway (self->priv->ipv4_config); + ip_string_to_network_address (gw_string, &gw); + + config = nm_ip4_config_new (); + + memset (&address, 0, sizeof (address)); + address.address = address_network; + address.plen = mm_bearer_ip_config_get_prefix (self->priv->ipv4_config); + address.source = NM_PLATFORM_SOURCE_WWAN; + nm_ip4_config_add_address (config, &address); + + nm_log_info (LOGD_MB, " address %s/%d", address_string, address.plen); + + if (gw) { + nm_ip4_config_set_gateway (config, gw); + nm_log_info (LOGD_MB, " gateway %s", gw_string); + } + + /* DNS servers */ + dns = mm_bearer_ip_config_get_dns (self->priv->ipv4_config); + for (i = 0; dns[i]; i++) { + if ( ip_string_to_network_address (dns[i], &address_network) + && address_network > 0) { + nm_ip4_config_add_nameserver (config, address_network); + nm_log_info (LOGD_MB, " DNS %s", dns[i]); + } + } + +out: + g_signal_emit_by_name (self, NM_MODEM_IP4_CONFIG_RESULT, config, error); + g_clear_error (&error); + return FALSE; +} + +static NMActStageReturn +static_stage3_ip4_config_start (NMModem *_self, + NMActRequest *req, + NMDeviceStateReason *reason) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + + /* We schedule it in an idle just to follow the same logic as in the + * generic modem implementation. */ + g_idle_add ((GSourceFunc)static_stage3_done, self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ +/* Disconnect */ + +typedef struct { + NMModemBroadband *self; + gboolean warn; +} SimpleDisconnectContext; + +static void +simple_disconnect_context_free (SimpleDisconnectContext *ctx) +{ + g_object_unref (ctx->self); + g_slice_free (SimpleDisconnectContext, ctx); +} + +static void +simple_disconnect_ready (MMModemSimple *modem_iface, + GAsyncResult *res, + SimpleDisconnectContext *ctx) +{ + GError *error = NULL; + + if (!mm_modem_simple_disconnect_finish (modem_iface, res, &error)) { + if (ctx->warn) + nm_log_warn (LOGD_MB, "(%s) failed to disconnect modem: %s", + nm_modem_get_uid (NM_MODEM (ctx->self)), + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + } + + simple_disconnect_context_free (ctx); +} + +static void +disconnect (NMModem *self, + gboolean warn) +{ + SimpleDisconnectContext *ctx; + + ctx = g_slice_new (SimpleDisconnectContext); + ctx->self = g_object_ref (self); + + /* Don't bother warning on FAILED since the modem is already gone */ + ctx->warn = warn; + + mm_modem_simple_disconnect ( + ctx->self->priv->simple_iface, + NULL, /* bearer path; if NULL given ALL get disconnected */ + NULL, /* cancellable */ + (GAsyncReadyCallback)simple_disconnect_ready, + ctx); +} + +/*****************************************************************************/ + +static void +deactivate (NMModem *_self, NMDevice *device) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (_self); + + /* TODO: cancel SimpleConnect() if any */ + + /* Cleanup IPv4 addresses and routes */ + g_clear_object (&self->priv->ipv4_config); + g_clear_object (&self->priv->ipv6_config); + g_clear_object (&self->priv->bearer); + + self->priv->pin_tries = 0; + + /* Chain up parent's */ + NM_MODEM_CLASS (nm_modem_broadband_parent_class)->deactivate (_self, device); +} + +/*****************************************************************************/ + +#define MAP_STATE(name) case MM_MODEM_STATE_##name: return NM_MODEM_STATE_##name; + +static NMModemState +mm_state_to_nm (MMModemState mm_state) +{ + switch (mm_state) { + MAP_STATE(UNKNOWN) + MAP_STATE(FAILED) + MAP_STATE(INITIALIZING) + MAP_STATE(LOCKED) + MAP_STATE(DISABLED) + MAP_STATE(DISABLING) + MAP_STATE(ENABLING) + MAP_STATE(ENABLED) + MAP_STATE(SEARCHING) + MAP_STATE(REGISTERED) + MAP_STATE(DISCONNECTING) + MAP_STATE(CONNECTING) + MAP_STATE(CONNECTED) + } + return NM_MODEM_STATE_UNKNOWN; +} + +static void +modem_state_changed (MMModem *modem, + MMModemState old_state, + MMModemState new_state, + MMModemStateChangeReason reason, + NMModemBroadband *self) +{ + + /* After the SIM is unlocked MM1 will move the device to INITIALIZING which + * is an unavailable state. That makes state handling confusing here, so + * suppress this state change and let the modem move from LOCKED to DISABLED. + */ + if (new_state == MM_MODEM_STATE_INITIALIZING && old_state == MM_MODEM_STATE_LOCKED) + return; + + nm_modem_set_state (NM_MODEM (self), + mm_state_to_nm (new_state), + mm_modem_state_change_reason_get_string (reason)); +} + +/*****************************************************************************/ + +NMModem * +nm_modem_broadband_new (GObject *object, GError **error) +{ + NMModem *modem; + MMObject *modem_object; + MMModem *modem_iface; + gchar *drivers; + + g_return_val_if_fail (MM_IS_OBJECT (object), NULL); + modem_object = MM_OBJECT (object); + + /* Ensure we have the 'Modem' interface and the primary port at least */ + modem_iface = mm_object_peek_modem (modem_object); + g_return_val_if_fail (!!modem_iface, NULL); + g_return_val_if_fail (!!mm_modem_get_primary_port (modem_iface), NULL); + + /* Build a single string with all drivers listed */ + drivers = g_strjoinv (", ", (gchar **)mm_modem_get_drivers (modem_iface)); + + modem = g_object_new (NM_TYPE_MODEM_BROADBAND, + NM_MODEM_PATH, mm_object_get_path (modem_object), + NM_MODEM_UID, mm_modem_get_primary_port (modem_iface), + NM_MODEM_CONTROL_PORT, mm_modem_get_primary_port (modem_iface), + NM_MODEM_DATA_PORT, NULL, /* We don't know it until bearer created */ + NM_MODEM_STATE, mm_state_to_nm (mm_modem_get_state (modem_iface)), + NM_MODEM_DEVICE_ID, mm_modem_get_device_identifier (modem_iface), + NM_MODEM_BROADBAND_MODEM, modem_object, + NM_MODEM_DRIVER, drivers, + NULL); + g_free (drivers); + return modem; +} + +static void +get_sim_ready (MMModem *modem, + GAsyncResult *res, + NMModemBroadband *self) +{ + GError *error = NULL; + MMSim *new_sim; + + new_sim = mm_modem_get_sim_finish (modem, res, &error); + if (new_sim) { + g_object_set (G_OBJECT (self), + NM_MODEM_SIM_ID, mm_sim_get_identifier (new_sim), + NULL); + g_object_unref (new_sim); + } else { + nm_log_warn (LOGD_MB, "(%s) failed to retrieve SIM object: %s", + nm_modem_get_uid (NM_MODEM (self)), + error && error->message ? error->message : "(unknown)"); + } + g_clear_error (&error); + g_object_unref (self); +} + +static void +sim_changed (MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (user_data); + + g_return_if_fail (modem == self->priv->modem_iface); + + if (mm_modem_get_sim_path (self->priv->modem_iface)) { + mm_modem_get_sim (self->priv->modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback) get_sim_ready, + g_object_ref (self)); + } else + g_object_set (G_OBJECT (self), NM_MODEM_SIM_ID, NULL, NULL); +} + +static void +nm_modem_broadband_init (NMModemBroadband *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + NM_TYPE_MODEM_BROADBAND, + NMModemBroadbandPrivate); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (object); + + switch (prop_id) { + case PROP_MODEM: + /* construct-only */ + self->priv->modem_object = g_value_dup_object (value); + self->priv->modem_iface = mm_object_get_modem (self->priv->modem_object); + g_assert (self->priv->modem_iface != NULL); + g_signal_connect (self->priv->modem_iface, + "state-changed", + G_CALLBACK (modem_state_changed), + self); + g_signal_connect (self->priv->modem_iface, + "notify::sim", + G_CALLBACK (sim_changed), + self); + sim_changed (self->priv->modem_iface, NULL, self); + + /* Note: don't grab the Simple iface here; the Modem interface is the + * only one assumed to be always valid and available */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object (value, self->priv->modem_object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND (object); + + g_clear_object (&self->priv->ipv4_config); + g_clear_object (&self->priv->ipv6_config); + g_clear_object (&self->priv->bearer); + g_clear_object (&self->priv->modem_iface); + g_clear_object (&self->priv->simple_iface); + g_clear_object (&self->priv->modem_object); + + G_OBJECT_CLASS (nm_modem_broadband_parent_class)->dispose (object); +} + +static void +nm_modem_broadband_class_init (NMModemBroadbandClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMModemClass *modem_class = NM_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMModemBroadbandPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + modem_class->get_capabilities = get_capabilities; + modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start; + modem_class->disconnect = disconnect; + modem_class->deactivate = deactivate; + modem_class->set_mm_enabled = set_mm_enabled; + modem_class->get_user_pass = get_user_pass; + modem_class->check_connection_compatible = check_connection_compatible; + modem_class->complete_connection = complete_connection; + modem_class->act_stage1_prepare = act_stage1_prepare; + modem_class->owns_port = owns_port; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_MODEM, + g_param_spec_object (NM_MODEM_BROADBAND_MODEM, + "Modem", + "Broadband modem object", + MM_GDBUS_TYPE_OBJECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} diff --git a/src/devices/wwan/nm-modem-broadband.h b/src/devices/wwan/nm-modem-broadband.h new file mode 100644 index 000000000..e12ca68ca --- /dev/null +++ b/src/devices/wwan/nm-modem-broadband.h @@ -0,0 +1,58 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 - Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef NM_MODEM_BROADBAND_H +#define NM_MODEM_BROADBAND_H + +#include <dbus/dbus-glib.h> +#include <glib-object.h> +#include "nm-modem.h" + +G_BEGIN_DECLS + +#define NM_TYPE_MODEM_BROADBAND (nm_modem_broadband_get_type ()) +#define NM_MODEM_BROADBAND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadband)) +#define NM_MODEM_BROADBAND_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass)) +#define NM_IS_MODEM_BROADBAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_MODEM_BROADBAND)) +#define NM_IS_MODEM_BROADBAND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_MODEM_BROADBAND)) +#define NM_MODEM_BROADBAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass)) + +#define NM_MODEM_BROADBAND_MODEM "modem" + +typedef struct _NMModemBroadband NMModemBroadband; +typedef struct _NMModemBroadbandClass NMModemBroadbandClass; +typedef struct _NMModemBroadbandPrivate NMModemBroadbandPrivate; + +struct _NMModemBroadband { + NMModem parent; + NMModemBroadbandPrivate *priv; +}; + +struct _NMModemBroadbandClass { + NMModemClass parent; +}; + +GType nm_modem_broadband_get_type (void); + +NMModem *nm_modem_broadband_new (GObject *object, GError **error); + +G_END_DECLS + +#endif /* NM_MODEM_BROADBAND_H */ diff --git a/src/devices/wwan/nm-modem-enum-types.c b/src/devices/wwan/nm-modem-enum-types.c new file mode 100644 index 000000000..cf59ba2b9 --- /dev/null +++ b/src/devices/wwan/nm-modem-enum-types.c @@ -0,0 +1,64 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#include "nm-modem-enum-types.h" + +#include "nm-modem.h" + +GType +nm_modem_error_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { NM_MODEM_ERROR_CONNECTION_NOT_GSM, "NM_MODEM_ERROR_CONNECTION_NOT_GSM", "ConnectionNotGsm" }, + { NM_MODEM_ERROR_CONNECTION_NOT_CDMA, "NM_MODEM_ERROR_CONNECTION_NOT_CDMA", "ConnectionNotCdma" }, + { NM_MODEM_ERROR_CONNECTION_INVALID, "NM_MODEM_ERROR_CONNECTION_INVALID", "ConnectionInvalid" }, + { NM_MODEM_ERROR_CONNECTION_INCOMPATIBLE, "NM_MODEM_ERROR_CONNECTION_INCOMPATIBLE", "ConnectionIncompatible" }, + { NM_MODEM_ERROR_INITIALIZATION_FAILED, "NM_MODEM_ERROR_INITIALIZATION_FAILED", "InitializationFailed" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("NMModemError"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} +GType +nm_modem_state_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { NM_MODEM_STATE_UNKNOWN, "NM_MODEM_STATE_UNKNOWN", "unknown" }, + { NM_MODEM_STATE_FAILED, "NM_MODEM_STATE_FAILED", "failed" }, + { NM_MODEM_STATE_INITIALIZING, "NM_MODEM_STATE_INITIALIZING", "initializing" }, + { NM_MODEM_STATE_LOCKED, "NM_MODEM_STATE_LOCKED", "locked" }, + { NM_MODEM_STATE_DISABLED, "NM_MODEM_STATE_DISABLED", "disabled" }, + { NM_MODEM_STATE_DISABLING, "NM_MODEM_STATE_DISABLING", "disabling" }, + { NM_MODEM_STATE_ENABLING, "NM_MODEM_STATE_ENABLING", "enabling" }, + { NM_MODEM_STATE_ENABLED, "NM_MODEM_STATE_ENABLED", "enabled" }, + { NM_MODEM_STATE_SEARCHING, "NM_MODEM_STATE_SEARCHING", "searching" }, + { NM_MODEM_STATE_REGISTERED, "NM_MODEM_STATE_REGISTERED", "registered" }, + { NM_MODEM_STATE_DISCONNECTING, "NM_MODEM_STATE_DISCONNECTING", "disconnecting" }, + { NM_MODEM_STATE_CONNECTING, "NM_MODEM_STATE_CONNECTING", "connecting" }, + { NM_MODEM_STATE_CONNECTED, "NM_MODEM_STATE_CONNECTED", "connected" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("NMModemState"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + + + diff --git a/src/devices/wwan/nm-modem-enum-types.h b/src/devices/wwan/nm-modem-enum-types.h new file mode 100644 index 000000000..81ccd8d2f --- /dev/null +++ b/src/devices/wwan/nm-modem-enum-types.h @@ -0,0 +1,21 @@ + + + +/* Generated by glib-mkenums. Do not edit */ + +#ifndef __NM_MODEM_ENUM_TYPES_H__ +#define __NM_MODEM_ENUM_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS +GType nm_modem_error_get_type (void) G_GNUC_CONST; +#define NM_TYPE_MODEM_ERROR (nm_modem_error_get_type ()) +GType nm_modem_state_get_type (void) G_GNUC_CONST; +#define NM_TYPE_MODEM_STATE (nm_modem_state_get_type ()) +G_END_DECLS + +#endif /* __NM_MODEM_ENUM_TYPES_H__ */ + + + diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c new file mode 100644 index 000000000..c48117004 --- /dev/null +++ b/src/devices/wwan/nm-modem-manager.c @@ -0,0 +1,757 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + * Copyright (C) 2009 Canonical Ltd. + */ + +#include <string.h> +#include "config.h" +#include "nm-modem-manager.h" +#include "nm-logging.h" +#include "nm-modem.h" +#include "nm-modem-old.h" +#include "nm-dbus-manager.h" +#include "nm-modem-old-types.h" +#include "nm-dbus-glib-types.h" + +#if WITH_MODEM_MANAGER_1 +#include <libmm-glib.h> +#include "nm-modem-broadband.h" +#endif + +#define MODEM_POKE_INTERVAL 120 + +G_DEFINE_TYPE (NMModemManager, nm_modem_manager, G_TYPE_OBJECT) + +struct _NMModemManagerPrivate { + /* ModemManager < 0.7 */ + NMDBusManager *dbus_mgr; + DBusGProxy *proxy; + guint poke_id; + +#if WITH_MODEM_MANAGER_1 + /* ModemManager >= 0.7 */ + GDBusConnection *dbus_connection; + MMManager *modem_manager_1; + guint modem_manager_1_launch_id; + gboolean old_modem_manager_found; + gboolean new_modem_manager_found; + guint modem_manager_1_name_owner_changed_id; + guint modem_manager_1_object_added_id; + guint modem_manager_1_object_removed_id; +#endif + + /* Common */ + GHashTable *modems; +}; + +enum { + MODEM_ADDED, + LAST_SIGNAL, +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +/************************************************************************/ + +static void +handle_new_modem (NMModemManager *self, NMModem *modem) +{ + const char *path; + + path = nm_modem_get_path (modem); + if (g_hash_table_lookup (self->priv->modems, path)) { + g_warn_if_reached (); + return; + } + + /* Track the new modem */ + g_hash_table_insert (self->priv->modems, g_strdup (path), modem); + g_signal_emit (self, signals[MODEM_ADDED], 0, modem); +} + +/************************************************************************/ +/* Support for ModemManager < 0.7 */ + +static void +clear_modem_manager_support (NMModemManager *self) +{ + if (self->priv->poke_id) { + g_source_remove (self->priv->poke_id); + self->priv->poke_id = 0; + } + + if (self->priv->proxy) { + g_object_unref (self->priv->proxy); + self->priv->proxy = NULL; + } +} + +static void +create_modem (NMModemManager *self, const char *path) +{ + DBusGProxy *proxy; + GError *error = NULL; + NMModem *modem = NULL; + GHashTable *properties; + + if (g_hash_table_lookup (self->priv->modems, path)) { + nm_log_warn (LOGD_MB, "modem with path %s already exists, ignoring", path); + return; + } + + proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (self->priv->dbus_mgr), + MM_OLD_DBUS_SERVICE, + path, + DBUS_INTERFACE_PROPERTIES); + g_assert (proxy); + if (dbus_g_proxy_call_with_timeout (proxy, "GetAll", 15000, &error, + G_TYPE_STRING, MM_OLD_DBUS_INTERFACE_MODEM, + G_TYPE_INVALID, + DBUS_TYPE_G_MAP_OF_VARIANT, &properties, + G_TYPE_INVALID)) { + /* Success, create the modem */ + modem = nm_modem_old_new (path, properties, &error); + if (modem) + handle_new_modem (self, modem); + else { + nm_log_warn (LOGD_MB, "failed to create modem: %s", + error ? error->message : "(unknown)"); + } + g_hash_table_destroy (properties); + } else { + nm_log_warn (LOGD_MB, "could not get modem properties: %s %s", + error ? dbus_g_error_get_name (error) : "(none)", + error ? error->message : "(unknown)"); + } + + g_object_unref (proxy); + g_clear_error (&error); +} + +static void +modem_added (DBusGProxy *proxy, const char *path, gpointer user_data) +{ + create_modem (NM_MODEM_MANAGER (user_data), path); +} + +static void +modem_removed (DBusGProxy *proxy, const char *path, gpointer user_data) +{ + NMModemManager *self = NM_MODEM_MANAGER (user_data); + NMModem *modem; + + modem = (NMModem *) g_hash_table_lookup (self->priv->modems, path); + if (modem) { + nm_modem_emit_removed (modem); + g_hash_table_remove (self->priv->modems, path); + } +} + +static void +mm_poke_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) +{ + GPtrArray *modems; + int i; + + if (dbus_g_proxy_end_call (proxy, call, NULL, + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, &modems, + G_TYPE_INVALID)) { + /* Don't care about the returned value, just free it */ + for (i = 0; i < modems->len; i++) + g_free ((char *) g_ptr_array_index (modems, i)); + g_ptr_array_free (modems, TRUE); + } + g_object_unref (proxy); +} + +static gboolean +poke_modem_cb (gpointer user_data) +{ + NMModemManager *self = NM_MODEM_MANAGER (user_data); + DBusGConnection *g_connection; + DBusGProxy *proxy; + + g_connection = nm_dbus_manager_get_connection (self->priv->dbus_mgr); + proxy = dbus_g_proxy_new_for_name (g_connection, + MM_OLD_DBUS_SERVICE, + MM_OLD_DBUS_PATH, + MM_OLD_DBUS_INTERFACE); + + nm_log_dbg (LOGD_MB, "Requesting to (re)launch modem-manager..."); + + dbus_g_proxy_begin_call_with_timeout (proxy, + "EnumerateDevices", + mm_poke_cb, + NULL, + NULL, + 5000, + G_TYPE_INVALID); + return TRUE; +} + +static void +enumerate_devices_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer data) +{ + NMModemManager *manager = NM_MODEM_MANAGER (data); + GPtrArray *modems; + GError *error = NULL; + + if (!dbus_g_proxy_end_call (proxy, call_id, &error, + dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &modems, + G_TYPE_INVALID)) { + nm_log_warn (LOGD_MB, "could not get modem list: %s", error->message); + g_error_free (error); + } else { + int i; + + for (i = 0; i < modems->len; i++) { + char *path = (char *) g_ptr_array_index (modems, i); + + create_modem (manager, path); + g_free (path); + } + + g_ptr_array_free (modems, TRUE); + } +} + +#if WITH_MODEM_MANAGER_1 +static void clear_modem_manager_1_support (NMModemManager *self); +#endif + +static void +modem_manager_appeared (NMModemManager *self, gboolean enumerate_devices) +{ + if (self->priv->poke_id) { + g_source_remove (self->priv->poke_id); + self->priv->poke_id = 0; + } + + nm_log_info (LOGD_MB, "modem-manager is now available"); + +#if WITH_MODEM_MANAGER_1 + self->priv->old_modem_manager_found = TRUE; + if (self->priv->new_modem_manager_found) + nm_log_warn (LOGD_MB, "Both the old and the new ModemManager were found"); + else + clear_modem_manager_1_support (self); +#endif + + self->priv->proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (self->priv->dbus_mgr), + MM_OLD_DBUS_SERVICE, MM_OLD_DBUS_PATH, MM_OLD_DBUS_INTERFACE); + + dbus_g_proxy_add_signal (self->priv->proxy, "DeviceAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (self->priv->proxy, "DeviceAdded", + G_CALLBACK (modem_added), self, + NULL); + + dbus_g_proxy_add_signal (self->priv->proxy, "DeviceRemoved", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (self->priv->proxy, "DeviceRemoved", + G_CALLBACK (modem_removed), self, + NULL); + + if (enumerate_devices) + dbus_g_proxy_begin_call (self->priv->proxy, "EnumerateDevices", enumerate_devices_done, self, NULL, G_TYPE_INVALID); +} + +static gboolean +remove_one_modem (gpointer key, gpointer value, gpointer user_data) +{ + nm_modem_emit_removed (NM_MODEM (value)); + return TRUE; +} + +static void +modem_manager_disappeared (NMModemManager *self) +{ + g_hash_table_foreach_remove (self->priv->modems, remove_one_modem, self); + + if (self->priv->proxy) { + g_object_unref (self->priv->proxy); + self->priv->proxy = NULL; + } + + /* Try to activate the modem-manager */ + nm_log_dbg (LOGD_MB, "trying to start the modem manager..."); + poke_modem_cb (self); + self->priv->poke_id = g_timeout_add_seconds (MODEM_POKE_INTERVAL, poke_modem_cb, self); +} + +static void +nm_modem_manager_name_owner_changed (NMDBusManager *dbus_mgr, + const char *name, + const char *old_owner, + const char *new_owner, + gpointer user_data) +{ + gboolean old_owner_good; + gboolean new_owner_good; + + /* Can't handle the signal if its not from the modem service */ + if (strcmp (MM_OLD_DBUS_SERVICE, name) != 0) + return; + + old_owner_good = (old_owner && strlen (old_owner)); + new_owner_good = (new_owner && strlen (new_owner)); + + if (!old_owner_good && new_owner_good) { + modem_manager_appeared (NM_MODEM_MANAGER (user_data), FALSE); + } else if (old_owner_good && !new_owner_good) { + nm_log_info (LOGD_MB, "the modem manager disappeared"); + modem_manager_disappeared (NM_MODEM_MANAGER (user_data)); + } +} + +/************************************************************************/ +/* Support for ModemManager >= 0.7 */ + +#if WITH_MODEM_MANAGER_1 + +static void +modem_manager_1_clear_signals (NMModemManager *self) +{ + if (!self->priv->modem_manager_1) + return; + + if (self->priv->modem_manager_1_name_owner_changed_id) { + if (g_signal_handler_is_connected (self->priv->modem_manager_1, + self->priv->modem_manager_1_name_owner_changed_id)) + g_signal_handler_disconnect (self->priv->modem_manager_1, + self->priv->modem_manager_1_name_owner_changed_id); + self->priv->modem_manager_1_name_owner_changed_id = 0; + } + + if (self->priv->modem_manager_1_object_added_id) { + if (g_signal_handler_is_connected (self->priv->modem_manager_1, + self->priv->modem_manager_1_object_added_id)) + g_signal_handler_disconnect (self->priv->modem_manager_1, + self->priv->modem_manager_1_object_added_id); + self->priv->modem_manager_1_object_added_id = 0; + } + + if (self->priv->modem_manager_1_object_removed_id) { + if (g_signal_handler_is_connected (self->priv->modem_manager_1, + self->priv->modem_manager_1_object_removed_id)) + g_signal_handler_disconnect (self->priv->modem_manager_1, + self->priv->modem_manager_1_object_removed_id); + self->priv->modem_manager_1_object_removed_id = 0; + } +} + +static void +clear_modem_manager_1_support (NMModemManager *self) +{ + if (self->priv->modem_manager_1_launch_id) { + g_source_remove (self->priv->modem_manager_1_launch_id); + self->priv->modem_manager_1_launch_id = 0; + } + + modem_manager_1_clear_signals (self); + g_clear_object (&self->priv->modem_manager_1); + g_clear_object (&self->priv->dbus_connection); +} + +static void +modem_object_added (MMManager *modem_manager, + MMObject *modem_object, + NMModemManager *self) +{ + const gchar *path; + MMModem *modem_iface; + NMModem *modem; + GError *error = NULL; + + /* Ensure we don't have the same modem already */ + path = mm_object_get_path (modem_object); + if (g_hash_table_lookup (self->priv->modems, path)) { + nm_log_warn (LOGD_MB, "modem with path %s already exists, ignoring", path); + return; + } + + /* Ensure we have the 'Modem' interface at least */ + modem_iface = mm_object_peek_modem (modem_object); + if (!modem_iface) { + nm_log_warn (LOGD_MB, "modem with path %s doesn't have the Modem interface, ignoring", path); + return; + } + + /* Ensure we have a primary port reported */ + if (!mm_modem_get_primary_port (modem_iface)) { + nm_log_warn (LOGD_MB, "modem with path %s has unknown primary port, ignoring", path); + return; + } + + /* Create a new modem object */ + modem = nm_modem_broadband_new (G_OBJECT (modem_object), &error); + if (modem) + handle_new_modem (self, modem); + else { + nm_log_warn (LOGD_MB, "failed to create modem: %s", + error ? error->message : "(unknown)"); + } + g_clear_error (&error); +} + +static void +modem_object_removed (MMManager *manager, + MMObject *modem_object, + NMModemManager *self) +{ + NMModem *modem; + const gchar *path; + + path = mm_object_get_path (modem_object); + modem = (NMModem *) g_hash_table_lookup (self->priv->modems, path); + if (!modem) + return; + + nm_modem_emit_removed (modem); + g_hash_table_remove (self->priv->modems, path); +} + +static void +modem_manager_1_available (NMModemManager *self) +{ + GList *modems, *l; + + nm_log_info (LOGD_MB, "ModemManager available in the bus"); + + self->priv->new_modem_manager_found = TRUE; + if (self->priv->old_modem_manager_found) + nm_log_warn (LOGD_MB, "Both the old and the new ModemManager were found"); + else + clear_modem_manager_support (self); + + /* Update initial modems list */ + modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->priv->modem_manager_1)); + for (l = modems; l; l = g_list_next (l)) + modem_object_added (self->priv->modem_manager_1, MM_OBJECT (l->data), self); + g_list_free_full (modems, (GDestroyNotify) g_object_unref); +} + +static void schedule_modem_manager_1_relaunch (NMModemManager *self, + guint n_seconds); +static void ensure_client (NMModemManager *self); + +static void +modem_manager_1_name_owner_changed (MMManager *modem_manager_1, + GParamSpec *pspec, + NMModemManager *self) +{ + gchar *name_owner; + + /* Quit poking, if any */ + if (self->priv->modem_manager_1_launch_id) { + g_source_remove (self->priv->modem_manager_1_launch_id); + self->priv->modem_manager_1_launch_id = 0; + } + + name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (modem_manager_1)); + if (!name_owner) { + nm_log_info (LOGD_MB, "ModemManager disappeared from bus"); + +#if !HAVE_SYSTEMD + /* If not managed by systemd, schedule relaunch */ + schedule_modem_manager_1_relaunch (self, 0); +#endif + + return; + } + + /* Available! */ + g_free (name_owner); + + /* Hack alert: GDBusObjectManagerClient won't signal neither 'object-added' + * nor 'object-removed' if it was created while there was no ModemManager in + * the bus. This hack avoids this issue until we get a GIO with the fix + * included... */ + modem_manager_1_clear_signals (self); + g_clear_object (&self->priv->modem_manager_1); + ensure_client (self); + + /* Whenever GDBusObjectManagerClient is fixed, we can just do the following: + * modem_manager_1_available (self); + */ +} + +#if !HAVE_SYSTEMD + +static void +modem_manager_1_poke_cb (GDBusConnection *connection, + GAsyncResult *res, + NMModemManager *self) +{ + GError *error = NULL; + GVariant *result; + + result = g_dbus_connection_call_finish (connection, res, &error); + if (error) { + /* Ignore common errors when MM is not installed and such */ + if ( !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) + && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED) + && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED) + && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED) + && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT) + && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) { + nm_log_dbg (LOGD_MB, "error poking ModemManager: %s", error->message); + } + g_error_free (error); + + /* Setup timeout to relaunch */ + schedule_modem_manager_1_relaunch (self, MODEM_POKE_INTERVAL); + } else + g_variant_unref (result); + + /* Balance refcount */ + g_object_unref (self); +} + +static void +modem_manager_1_poke (NMModemManager *self) +{ + /* If there is no current owner right away, ensure we poke to get one */ + g_dbus_connection_call (self->priv->dbus_connection, + "org.freedesktop.ModemManager1", + "/org/freedesktop/ModemManager1", + "org.freedesktop.DBus.Peer", + "Ping", + NULL, /* inputs */ + NULL, /* outputs */ + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* cancellable */ + (GAsyncReadyCallback)modem_manager_1_poke_cb, /* callback */ + g_object_ref (self)); /* user_data */ +} + +#endif /* HAVE_SYSTEMD */ + +static void +modem_manager_1_check_name_owner (NMModemManager *self) +{ + gchar *name_owner; + + name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (self->priv->modem_manager_1)); + if (name_owner) { + /* Available! */ + modem_manager_1_available (self); + g_free (name_owner); + return; + } + +#if !HAVE_SYSTEMD + /* If the lifecycle is not managed by systemd, poke */ + modem_manager_1_poke (self); +#endif +} + +static void +manager_new_ready (GObject *source, + GAsyncResult *res, + NMModemManager *self) +{ + /* Note we always get an extra reference to self here */ + + GError *error = NULL; + + g_assert (!self->priv->modem_manager_1); + self->priv->modem_manager_1 = mm_manager_new_finish (res, &error); + if (!self->priv->modem_manager_1) { + /* We're not really supposed to get any error here. If we do get one, + * though, just re-schedule the MMManager creation after some time. + * During this period, name-owner changes won't be followed. */ + nm_log_warn (LOGD_MB, "error creating ModemManager client: %s", error->message); + g_error_free (error); + /* Setup timeout to relaunch */ + schedule_modem_manager_1_relaunch (self, MODEM_POKE_INTERVAL); + } else if (self->priv->old_modem_manager_found) { + /* If we found the old MM, abort */ + clear_modem_manager_1_support (self); + } else { + /* Setup signals in the GDBusObjectManagerClient */ + self->priv->modem_manager_1_name_owner_changed_id = + g_signal_connect (self->priv->modem_manager_1, + "notify::name-owner", + G_CALLBACK (modem_manager_1_name_owner_changed), + self); + self->priv->modem_manager_1_object_added_id = + g_signal_connect (self->priv->modem_manager_1, + "object-added", + G_CALLBACK (modem_object_added), + self); + self->priv->modem_manager_1_object_removed_id = + g_signal_connect (self->priv->modem_manager_1, + "object-removed", + G_CALLBACK (modem_object_removed), + self); + + modem_manager_1_check_name_owner (self); + } + + /* Balance refcount */ + g_object_unref (self); +} + +static void +ensure_client (NMModemManager *self) +{ + g_assert (self->priv->dbus_connection); + + /* Create the GDBusObjectManagerClient. We do not request to autostart, as + * we don't really want the MMManager creation to fail. We can always poke + * later on if we want to request the autostart */ + if (!self->priv->modem_manager_1) { + mm_manager_new (self->priv->dbus_connection, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, + NULL, + (GAsyncReadyCallback)manager_new_ready, + g_object_ref (self)); + return; + } + + /* If already available, recheck name owner! */ + modem_manager_1_check_name_owner (self); +} + +static void +bus_get_ready (GObject *source, + GAsyncResult *res, + NMModemManager *self) +{ + /* Note we always get an extra reference to self here */ + + GError *error = NULL; + + self->priv->dbus_connection = g_bus_get_finish (res, &error); + if (!self->priv->dbus_connection) { + nm_log_warn (LOGD_CORE, "error getting bus connection: %s", error->message); + g_error_free (error); + /* Setup timeout to relaunch */ + schedule_modem_manager_1_relaunch (self, MODEM_POKE_INTERVAL); + } else if (self->priv->old_modem_manager_found) { + /* If we found the old MM, abort */ + clear_modem_manager_1_support (self); + } else { + /* Got the bus, ensure client */ + ensure_client (self); + } + + /* Balance refcount */ + g_object_unref (self); +} + +static gboolean +ensure_bus (NMModemManager *self) +{ + /* Clear launch ID */ + self->priv->modem_manager_1_launch_id = 0; + + if (!self->priv->dbus_connection) + g_bus_get (G_BUS_TYPE_SYSTEM, + NULL, + (GAsyncReadyCallback)bus_get_ready, + g_object_ref (self)); + else + /* If bus is already available, ensure client */ + ensure_client (self); + + return FALSE; +} + +static void +schedule_modem_manager_1_relaunch (NMModemManager *self, + guint n_seconds) +{ + /* No need to pass an extra reference to self; timeout/idle will be + * cancelled if the object gets disposed. */ + + if (n_seconds) + self->priv->modem_manager_1_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)ensure_bus, self); + else + self->priv->modem_manager_1_launch_id = g_idle_add ((GSourceFunc)ensure_bus, self); +} + +#endif /* WITH_MODEM_MANAGER_1 */ + +/************************************************************************/ + +static void +nm_modem_manager_init (NMModemManager *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_MODEM_MANAGER, NMModemManagerPrivate); + + self->priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + + /* ModemManager < 0.7 */ + self->priv->dbus_mgr = nm_dbus_manager_get (); + g_signal_connect (self->priv->dbus_mgr, NM_DBUS_MANAGER_NAME_OWNER_CHANGED, + G_CALLBACK (nm_modem_manager_name_owner_changed), + self); + if (nm_dbus_manager_name_has_owner (self->priv->dbus_mgr, MM_OLD_DBUS_SERVICE)) + modem_manager_appeared (self, TRUE); + else + modem_manager_disappeared (self); + +#if WITH_MODEM_MANAGER_1 + /* ModemManager >= 0.7 */ + schedule_modem_manager_1_relaunch (self, 0); +#endif +} + +static void +dispose (GObject *object) +{ + NMModemManager *self = NM_MODEM_MANAGER (object); + + /* ModemManager < 0.7 */ + clear_modem_manager_support (self); + +#if WITH_MODEM_MANAGER_1 + /* ModemManager >= 0.7 */ + clear_modem_manager_1_support (self); +#endif + + if (self->priv->modems) { + g_hash_table_foreach_remove (self->priv->modems, remove_one_modem, object); + g_hash_table_destroy (self->priv->modems); + } + + self->priv->dbus_mgr = NULL; + + /* Chain up to the parent class */ + G_OBJECT_CLASS (nm_modem_manager_parent_class)->dispose (object); +} + +static void +nm_modem_manager_class_init (NMModemManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMModemManagerPrivate)); + + object_class->dispose = dispose; + + signals[MODEM_ADDED] = + g_signal_new (NM_MODEM_MANAGER_MODEM_ADDED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemManagerClass, modem_added), + NULL, NULL, NULL, + G_TYPE_NONE, 1, NM_TYPE_MODEM); +} diff --git a/src/devices/wwan/nm-modem-manager.h b/src/devices/wwan/nm-modem-manager.h new file mode 100644 index 000000000..3082bdb39 --- /dev/null +++ b/src/devices/wwan/nm-modem-manager.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + * Copyright (C) 2009 Canonical Ltd. + */ + +#ifndef NM_MODEM_MANAGER_H +#define NM_MODEM_MANAGER_H + +#include <glib-object.h> +#include "nm-modem.h" + +#define NM_TYPE_MODEM_MANAGER (nm_modem_manager_get_type ()) +#define NM_MODEM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_MANAGER, NMModemManager)) + +#define NM_MODEM_MANAGER_MODEM_ADDED "modem-added" + +typedef struct _NMModemManagerPrivate NMModemManagerPrivate; + +typedef struct { + GObject parent; + NMModemManagerPrivate *priv; +} NMModemManager; + +typedef struct { + GObjectClass parent; + + void (*modem_added) (NMModemManager *self, NMModem *modem); +} NMModemManagerClass; + +GType nm_modem_manager_get_type (void); + +#endif /* NM_MODEM_MANAGER_H */ diff --git a/src/devices/wwan/nm-modem-old-types.h b/src/devices/wwan/nm-modem-old-types.h new file mode 100644 index 000000000..84065a684 --- /dev/null +++ b/src/devices/wwan/nm-modem-old-types.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef NM_MODEM_OLD_TYPES_H +#define NM_MODEM_OLD_TYPES_H + +#define MM_OLD_DBUS_SERVICE "org.freedesktop.ModemManager" +#define MM_OLD_DBUS_PATH "/org/freedesktop/ModemManager" +#define MM_OLD_DBUS_INTERFACE "org.freedesktop.ModemManager" +#define MM_OLD_DBUS_INTERFACE_MODEM "org.freedesktop.ModemManager.Modem" +#define MM_OLD_DBUS_INTERFACE_MODEM_SIMPLE "org.freedesktop.ModemManager.Modem.Simple" +#define MM_OLD_DBUS_INTERFACE_MODEM_CDMA "org.freedesktop.ModemManager.Modem.Cdma" +#define MM_OLD_DBUS_INTERFACE_MODEM_GSM_CARD "org.freedesktop.ModemManager.Modem.Gsm.Card" +#define MM_OLD_DBUS_INTERFACE_MODEM_GSM_NETWORK "org.freedesktop.ModemManager.Modem.Gsm.Network" + +#define MM_OLD_MODEM_TYPE_UNKNOWN 0 +#define MM_OLD_MODEM_TYPE_GSM 1 +#define MM_OLD_MODEM_TYPE_CDMA 2 + +/* Errors */ + +#define MM_OLD_MODEM_CONNECT_ERROR_NO_CARRIER MM_OLD_DBUS_INTERFACE_MODEM ".NoCarrier" +#define MM_OLD_MODEM_CONNECT_ERROR_NO_DIALTONE MM_OLD_DBUS_INTERFACE_MODEM ".NoDialtone" +#define MM_OLD_MODEM_CONNECT_ERROR_BUSY MM_OLD_DBUS_INTERFACE_MODEM ".Busy" +#define MM_OLD_MODEM_CONNECT_ERROR_NO_ANSWER MM_OLD_DBUS_INTERFACE_MODEM ".NoAnswer" + +#define MM_OLD_MODEM_ERROR "org.freedesktop.ModemManager.Modem.Gsm" + +#define MM_OLD_MODEM_ERROR_NETWORK_NOT_ALLOWED MM_OLD_MODEM_ERROR ".NetworkNotAllowed" +#define MM_OLD_MODEM_ERROR_NETWORK_TIMEOUT MM_OLD_MODEM_ERROR ".NetworkTimeout" +#define MM_OLD_MODEM_ERROR_NO_NETWORK MM_OLD_MODEM_ERROR ".NoNetwork" +#define MM_OLD_MODEM_ERROR_SIM_NOT_INSERTED MM_OLD_MODEM_ERROR ".SimNotInserted" +#define MM_OLD_MODEM_ERROR_SIM_PIN MM_OLD_MODEM_ERROR ".SimPinRequired" +#define MM_OLD_MODEM_ERROR_SIM_PUK MM_OLD_MODEM_ERROR ".SimPukRequired" +#define MM_OLD_MODEM_ERROR_SIM_WRONG MM_OLD_MODEM_ERROR ".SimWrong" +#define MM_OLD_MODEM_ERROR_WRONG_PASSWORD MM_OLD_MODEM_ERROR ".IncorrectPassword" + +typedef enum { + MM_OLD_MODEM_STATE_UNKNOWN = 0, + MM_OLD_MODEM_STATE_DISABLED = 10, + MM_OLD_MODEM_STATE_DISABLING = 20, + MM_OLD_MODEM_STATE_ENABLING = 30, + MM_OLD_MODEM_STATE_ENABLED = 40, + MM_OLD_MODEM_STATE_SEARCHING = 50, + MM_OLD_MODEM_STATE_REGISTERED = 60, + MM_OLD_MODEM_STATE_DISCONNECTING = 70, + MM_OLD_MODEM_STATE_CONNECTING = 80, + MM_OLD_MODEM_STATE_CONNECTED = 90, + + MM_OLD_MODEM_STATE_LAST = MM_OLD_MODEM_STATE_CONNECTED +} MMOldModemState; + +#endif /* NM_MODEM_OLD_TYPES_H */ diff --git a/src/devices/wwan/nm-modem-old.c b/src/devices/wwan/nm-modem-old.c new file mode 100644 index 000000000..ce1a382e1 --- /dev/null +++ b/src/devices/wwan/nm-modem-old.c @@ -0,0 +1,1139 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2013 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#include <string.h> +#include <glib/gi18n.h> + +#include "nm-modem-old.h" +#include "nm-dbus-manager.h" +#include "nm-setting-connection.h" +#include "nm-properties-changed-signal.h" +#include "nm-modem-old-types.h" +#include "nm-logging.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-dbus-glib-types.h" +#include "nm-glib-compat.h" + +G_DEFINE_TYPE (NMModemOld, nm_modem_old, NM_TYPE_MODEM) + +#define NM_MODEM_OLD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_MODEM_OLD, NMModemOldPrivate)) + +typedef struct { + DBusGProxy *proxy; + DBusGProxy *props_proxy; + + MMOldModemState state; + NMDeviceModemCapabilities caps; + char *unlock_required; + + DBusGProxyCall *call; + GHashTable *connect_properties; + + guint32 pin_tries; + guint enable_delay_id; +} NMModemOldPrivate; + +#define CAPS_3GPP (NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS | NM_DEVICE_MODEM_CAPABILITY_LTE) + +/*****************************************************************************/ + +typedef enum { + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY = 0, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_GPRS, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_EDGE, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_UMTS, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSDPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_PREFERRED, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_PREFERRED, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_ONLY, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_ONLY, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSUPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA, + + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_LAST = MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA +} MMModemDeprecatedMode; + +typedef enum { + MM_MODEM_GSM_ALLOWED_MODE_ANY = 0, + MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED = 1, + MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED = 2, + MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY = 3, + MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY = 4, + MM_MODEM_GSM_ALLOWED_MODE_4G_PREFERRED = 5, + MM_MODEM_GSM_ALLOWED_MODE_4G_ONLY = 6, + + MM_MODEM_GSM_ALLOWED_MODE_LAST = MM_MODEM_GSM_ALLOWED_MODE_4G_ONLY +} MMModemGsmAllowedMode; + +typedef enum { + MM_MODEM_GSM_ALLOWED_AUTH_UNKNOWN = 0x0000, + /* bits 0..4 order match Ericsson device bitmap */ + MM_MODEM_GSM_ALLOWED_AUTH_NONE = 0x0001, + MM_MODEM_GSM_ALLOWED_AUTH_PAP = 0x0002, + MM_MODEM_GSM_ALLOWED_AUTH_CHAP = 0x0004, + MM_MODEM_GSM_ALLOWED_AUTH_MSCHAP = 0x0008, + MM_MODEM_GSM_ALLOWED_AUTH_MSCHAPV2 = 0x0010, + MM_MODEM_GSM_ALLOWED_AUTH_EAP = 0x0020, + + MM_MODEM_GSM_ALLOWED_AUTH_LAST = MM_MODEM_GSM_ALLOWED_AUTH_EAP +} MMModemGsmAllowedAuth; + +static NMDeviceStateReason +translate_mm_error (GError *error) +{ + NMDeviceStateReason reason; + + if (dbus_g_error_has_name (error, MM_OLD_MODEM_CONNECT_ERROR_NO_CARRIER)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_CONNECT_ERROR_NO_DIALTONE)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_CONNECT_ERROR_BUSY)) + reason = NM_DEVICE_STATE_REASON_MODEM_BUSY; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_CONNECT_ERROR_NO_ANSWER)) + reason = NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_NETWORK_NOT_ALLOWED)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_NETWORK_TIMEOUT)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_NO_NETWORK)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_SIM_NOT_INSERTED)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_SIM_PIN)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_SIM_PUK)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_SIM_WRONG)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_WRONG; + else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_WRONG_PASSWORD)) + reason = NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT; + else { + /* unable to map the ModemManager error to a NM_DEVICE_STATE_REASON */ + nm_log_dbg (LOGD_MB, "unmapped dbus error detected: '%s'", dbus_g_error_get_name (error)); + reason = NM_DEVICE_STATE_REASON_UNKNOWN; + } + + /* FIXME: We have only GSM error messages here, and we have no idea which + activation state failed. Reasons like: + NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED, + NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED, + NM_DEVICE_STATE_REASON_GSM_APN_FAILED, + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED, + NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED + are not used. + */ + return reason; +} + +#define MAP_STATE(name) case MM_OLD_MODEM_STATE_##name: return NM_MODEM_STATE_##name; + +static NMModemState +mm_state_to_nm (MMOldModemState mm_state, const char *unlock_required) +{ + if (unlock_required && *unlock_required) + return NM_MODEM_STATE_LOCKED; + + switch (mm_state) { + MAP_STATE(UNKNOWN) + MAP_STATE(DISABLED) + MAP_STATE(DISABLING) + MAP_STATE(ENABLING) + MAP_STATE(ENABLED) + MAP_STATE(SEARCHING) + MAP_STATE(REGISTERED) + MAP_STATE(DISCONNECTING) + MAP_STATE(CONNECTING) + MAP_STATE(CONNECTED) + } + return NM_MODEM_STATE_UNKNOWN; +}; + +/*****************************************************************************/ + +static DBusGProxy * +nm_modem_old_get_proxy (NMModemOld *self, const char *interface) +{ + + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + const char *current_iface; + + g_return_val_if_fail (NM_IS_MODEM_OLD (self), NULL); + + /* Default to the default interface. */ + if (interface == NULL) + interface = MM_OLD_DBUS_INTERFACE_MODEM; + + if (interface && !strcmp (interface, DBUS_INTERFACE_PROPERTIES)) + return priv->props_proxy; + + current_iface = dbus_g_proxy_get_interface (priv->proxy); + if (!current_iface || strcmp (current_iface, interface)) + dbus_g_proxy_set_interface (priv->proxy, interface); + + return priv->proxy; +} + +/*****************************************************************************/ +/* Query/Update enabled state */ + +static void +set_mm_enabled_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) +{ + GError *error = NULL; + + if (!dbus_g_proxy_end_call (proxy, call_id, &error, G_TYPE_INVALID)) { + nm_log_warn (LOGD_MB, "failed to enable/disable modem: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + nm_modem_set_prev_state (NM_MODEM (user_data), "enable/disable failed"); + } + /* Wait for the state change signal to indicate enabled state changed */ +} + +static void +set_mm_enabled (NMModem *self, gboolean enabled) +{ + dbus_g_proxy_begin_call (nm_modem_old_get_proxy (NM_MODEM_OLD (self), MM_OLD_DBUS_INTERFACE_MODEM), + "Enable", set_mm_enabled_done, + g_object_ref (self), g_object_unref, + G_TYPE_BOOLEAN, enabled, + G_TYPE_INVALID); +} + +/*****************************************************************************/ + +static void +ask_for_pin (NMModemOld *self, gboolean always_ask) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + guint32 tries = 0; + + g_return_if_fail (NM_IS_MODEM_OLD (self)); + + if (!always_ask) + tries = priv->pin_tries++; + + nm_modem_get_secrets (NM_MODEM (self), + NM_SETTING_GSM_SETTING_NAME, + (tries || always_ask) ? TRUE : FALSE, + NM_SETTING_GSM_PIN); +} + +static void +stage1_prepare_done (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) +{ + NMModemOld *self = NM_MODEM_OLD (user_data); + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + GError *error = NULL; + gboolean asked = FALSE; + + priv->call = NULL; + + if (priv->connect_properties) { + g_hash_table_destroy (priv->connect_properties); + priv->connect_properties = NULL; + } + + if (dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID)) + g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, TRUE, NM_DEVICE_STATE_REASON_NONE); + else { + if (priv->caps & CAPS_3GPP) { + if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_SIM_PIN)) { + ask_for_pin (self, FALSE); + asked = TRUE; + } else if (dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_SIM_WRONG)) { + ask_for_pin (self, TRUE); + asked = TRUE; + } + } + + if (!asked) { + nm_log_warn (LOGD_MB, "Mobile connection failed: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (error)); + } + g_error_free (error); + } +} + +static void +do_connect (NMModemOld *self) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + DBusGProxy *proxy; + + proxy = nm_modem_old_get_proxy (NM_MODEM_OLD (self), MM_OLD_DBUS_INTERFACE_MODEM_SIMPLE); + priv->call = dbus_g_proxy_begin_call_with_timeout (proxy, + "Connect", stage1_prepare_done, + self, NULL, 120000, + DBUS_TYPE_G_MAP_OF_VARIANT, priv->connect_properties, + G_TYPE_INVALID); +} + +static void stage1_enable_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data); + +/* do_enable() is used as a GSourceFunc, hence the gboolean return */ +static gboolean +do_enable (NMModemOld *self) +{ + DBusGProxy *proxy; + + g_return_val_if_fail (NM_IS_MODEM_OLD (self), FALSE); + + NM_MODEM_OLD_GET_PRIVATE (self)->enable_delay_id = 0; + proxy = nm_modem_old_get_proxy (NM_MODEM_OLD (self), MM_OLD_DBUS_INTERFACE_MODEM); + dbus_g_proxy_begin_call_with_timeout (proxy, + "Enable", stage1_enable_done, + self, NULL, 20000, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID); + return FALSE; +} + +static void +stage1_pin_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) +{ + NMModemOld *self = NM_MODEM_OLD (user_data); + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + NMDeviceStateReason reason; + GError *error = NULL; + + if (dbus_g_proxy_end_call (proxy, call_id, &error, G_TYPE_INVALID)) { + /* Success; try to enable the modem again. Wait a few seconds to ensure + * that ModemManager is ready for the enable right after the unlock. + */ + if (priv->enable_delay_id == 0) + priv->enable_delay_id = g_timeout_add_seconds (4, (GSourceFunc) do_enable, self); + } else { + nm_log_warn (LOGD_MB, "GSM PIN unlock failed: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + + /* try to translate the error reason */ + reason = translate_mm_error (error); + if (reason == NM_DEVICE_STATE_REASON_UNKNOWN) + reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED; + + g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, reason); + g_error_free (error); + } +} + +static void +handle_enable_pin_required (NMModemOld *self) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + const char *pin = NULL; + GValue *value; + DBusGProxy *proxy; + + g_assert (priv->caps & CAPS_3GPP); + + /* See if we have a PIN already */ + value = g_hash_table_lookup (priv->connect_properties, "pin"); + if (value && G_VALUE_HOLDS_STRING (value)) + pin = g_value_get_string (value); + + /* If we do, send it */ + if (pin) { + proxy = nm_modem_old_get_proxy (NM_MODEM_OLD (self), MM_OLD_DBUS_INTERFACE_MODEM_GSM_CARD); + dbus_g_proxy_begin_call_with_timeout (proxy, + "SendPin", stage1_pin_done, + self, NULL, 10000, + G_TYPE_STRING, pin, + G_TYPE_INVALID); + } else + ask_for_pin (self, FALSE); +} + +static void +stage1_enable_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) +{ + NMModemOld *self = NM_MODEM_OLD (user_data); + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + NMDeviceStateReason reason; + GError *error = NULL; + + if (dbus_g_proxy_end_call (proxy, call_id, &error, G_TYPE_INVALID)) + do_connect (self); + else { + if ((priv->caps & CAPS_3GPP) && dbus_g_error_has_name (error, MM_OLD_MODEM_ERROR_SIM_PIN)) + handle_enable_pin_required (self); + else { + nm_log_warn (LOGD_MB, "Modem enable failed: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + + /* try to translate the error reason */ + reason = translate_mm_error (error); + if (reason == NM_DEVICE_STATE_REASON_UNKNOWN) + reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED; + g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, reason); + } + + g_error_free (error); + } +} + +static GHashTable * +create_connect_properties (NMConnection *connection) +{ + NMSettingCdma *s_cdma; + NMSettingGsm *s_gsm; + NMSettingPPP *s_ppp; + GHashTable *properties; + const char *str; + + properties = value_hash_create (); + + s_cdma = nm_connection_get_setting_cdma (connection); + if (s_cdma) { + str = nm_setting_cdma_get_number (s_cdma); + if (str) + value_hash_add_str (properties, "number", str); + return properties; + } + + s_gsm = nm_connection_get_setting_gsm (connection); + if (s_gsm) { + str = nm_setting_gsm_get_number (s_gsm); + if (str) + value_hash_add_str (properties, "number", str); + + str = nm_setting_gsm_get_apn (s_gsm); + if (str) + value_hash_add_str (properties, "apn", str); + + str = nm_setting_gsm_get_network_id (s_gsm); + if (str) + value_hash_add_str (properties, "network_id", str); + + str = nm_setting_gsm_get_pin (s_gsm); + if (str) + value_hash_add_str (properties, "pin", str); + + str = nm_setting_gsm_get_username (s_gsm); + if (str) + value_hash_add_str (properties, "username", str); + + str = nm_setting_gsm_get_password (s_gsm); + if (str) + value_hash_add_str (properties, "password", str); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + /* Add both old and new preferred modes */ + switch (nm_setting_gsm_get_network_type (s_gsm)) { + case NM_SETTING_GSM_NETWORK_TYPE_UMTS_HSPA: + value_hash_add_uint (properties, "network_mode", MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_ONLY); + value_hash_add_uint (properties, "allowed_mode", MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY); + break; + case NM_SETTING_GSM_NETWORK_TYPE_GPRS_EDGE: + value_hash_add_uint (properties, "network_mode", MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_ONLY); + value_hash_add_uint (properties, "allowed_mode", MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY); + break; + case NM_SETTING_GSM_NETWORK_TYPE_PREFER_UMTS_HSPA: + value_hash_add_uint (properties, "network_mode", MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_PREFERRED); + value_hash_add_uint (properties, "allowed_mode", MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED); + break; + case NM_SETTING_GSM_NETWORK_TYPE_PREFER_GPRS_EDGE: + value_hash_add_uint (properties, "network_mode", MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_PREFERRED); + value_hash_add_uint (properties, "allowed_mode", MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED); + break; + case NM_SETTING_GSM_NETWORK_TYPE_PREFER_4G: + /* deprecated modes not extended for 4G, so no need to set them here */ + value_hash_add_uint (properties, "allowed_mode", MM_MODEM_GSM_ALLOWED_MODE_4G_PREFERRED); + break; + case NM_SETTING_GSM_NETWORK_TYPE_4G: + /* deprecated modes not extended for 4G, so no need to set them here */ + value_hash_add_uint (properties, "allowed_mode", MM_MODEM_GSM_ALLOWED_MODE_4G_ONLY); + break; + default: + value_hash_add_uint (properties, "network_mode", MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY); + value_hash_add_uint (properties, "allowed_mode", MM_MODEM_GSM_ALLOWED_MODE_ANY); + break; + } +G_GNUC_END_IGNORE_DEPRECATIONS + + /* Roaming */ + if (nm_setting_gsm_get_home_only (s_gsm)) + value_hash_add_bool (properties, "home_only", TRUE); + + /* For IpMethod == STATIC or DHCP */ + s_ppp = nm_connection_get_setting_ppp (connection); + if (s_ppp) { + guint32 auth = MM_MODEM_GSM_ALLOWED_AUTH_UNKNOWN; + + if (nm_setting_ppp_get_noauth (s_ppp)) + auth |= MM_MODEM_GSM_ALLOWED_AUTH_NONE; + if (!nm_setting_ppp_get_refuse_pap (s_ppp)) + auth |= MM_MODEM_GSM_ALLOWED_AUTH_PAP; + if (!nm_setting_ppp_get_refuse_chap (s_ppp)) + auth |= MM_MODEM_GSM_ALLOWED_AUTH_CHAP; + if (!nm_setting_ppp_get_refuse_mschap (s_ppp)) + auth |= MM_MODEM_GSM_ALLOWED_AUTH_MSCHAP; + if (!nm_setting_ppp_get_refuse_mschapv2 (s_ppp)) + auth |= MM_MODEM_GSM_ALLOWED_AUTH_MSCHAPV2; + if (!nm_setting_ppp_get_refuse_eap (s_ppp)) + auth |= MM_MODEM_GSM_ALLOWED_AUTH_EAP; + + if (auth != MM_MODEM_GSM_ALLOWED_AUTH_UNKNOWN) + value_hash_add_uint (properties, "allowed_auth", auth); + } + + return properties; + } + + g_hash_table_destroy (properties); + return NULL; +} + +static NMActStageReturn +act_stage1_prepare (NMModem *modem, + NMConnection *connection, + NMDeviceStateReason *reason) +{ + NMModemOld *self = NM_MODEM_OLD (modem); + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + + if (priv->connect_properties) + g_hash_table_destroy (priv->connect_properties); + priv->connect_properties = create_connect_properties (connection); + + if (nm_modem_get_state (modem) >= NM_MODEM_STATE_ENABLING) + do_connect (self); + else + do_enable (self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ +/* IP method static */ + +static char addr_to_string_buf[INET6_ADDRSTRLEN + 1]; + +static const char * +ip_address_to_string (guint32 numeric) +{ + guint32 temp_addr; + + memset (&addr_to_string_buf, '\0', sizeof (addr_to_string_buf)); + temp_addr = numeric; + + if (inet_ntop (AF_INET, &temp_addr, addr_to_string_buf, INET_ADDRSTRLEN)) { + return addr_to_string_buf; + } else { + nm_log_warn (LOGD_VPN, "error converting IP4 address 0x%X", + ntohl (temp_addr)); + return NULL; + } +} + +static void +static_stage3_done (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) +{ + NMModemOld *self = NM_MODEM_OLD (user_data); + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + GValueArray *ret_array = NULL; + GError *error = NULL; + NMIP4Config *config = NULL; + + priv->call = NULL; + + /* Returned value array is (uuuu): [IP, DNS1, DNS2, DNS3], all in + * network byte order. + */ + if (dbus_g_proxy_end_call (proxy, call, &error, + G_TYPE_VALUE_ARRAY, &ret_array, + G_TYPE_INVALID)) { + NMPlatformIP4Address address; + int i; + + config = nm_ip4_config_new (); + memset (&address, 0, sizeof (address)); + + nm_log_info (LOGD_MB, "(%s): IPv4 static configuration:", + nm_modem_get_uid (NM_MODEM (self))); + + /* IP address */ + address.address = g_value_get_uint (g_value_array_get_nth (ret_array, 0)); + address.plen = 32; + address.source = NM_PLATFORM_SOURCE_WWAN; + nm_ip4_config_add_address (config, &address); + + nm_log_info (LOGD_MB, " address %s/%d", + ip_address_to_string (address.address), + address.plen); + + /* DNS servers */ + for (i = 1; i < ret_array->n_values; i++) { + GValue *value = g_value_array_get_nth (ret_array, i); + guint32 tmp = g_value_get_uint (value); + + if (tmp > 0) { + nm_ip4_config_add_nameserver (config, tmp); + nm_log_info (LOGD_MB, " DNS %s", ip_address_to_string (tmp)); + } + } + g_value_array_free (ret_array); + } + + g_signal_emit_by_name (self, NM_MODEM_IP4_CONFIG_RESULT, config, error); + g_clear_error (&error); +} + +static NMActStageReturn +static_stage3_ip4_config_start (NMModem *self, + NMActRequest *req, + NMDeviceStateReason *reason) +{ + NMModemOldPrivate *priv; + + g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (NM_IS_ACT_REQUEST (req), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + priv = NM_MODEM_OLD_GET_PRIVATE (self); + + priv->call = dbus_g_proxy_begin_call (nm_modem_old_get_proxy (NM_MODEM_OLD (self), + MM_OLD_DBUS_INTERFACE_MODEM), + "GetIP4Config", static_stage3_done, + self, NULL, + G_TYPE_INVALID); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static void +disconnect_done (DBusGProxy *proxy, + DBusGProxyCall *call_id, + gpointer user_data) +{ + GError *error = NULL; + gboolean warn = GPOINTER_TO_UINT (user_data); + + if (!dbus_g_proxy_end_call (proxy, call_id, &error, G_TYPE_INVALID) && warn) { + nm_log_info (LOGD_MB, "disconnect failed: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + } +} + +static void +disconnect (NMModem *self, + gboolean warn) +{ + dbus_g_proxy_begin_call (nm_modem_old_get_proxy (NM_MODEM_OLD (self), + MM_OLD_DBUS_INTERFACE_MODEM), + "Disconnect", + disconnect_done, + GUINT_TO_POINTER (warn), + NULL, + G_TYPE_INVALID); +} + +/*****************************************************************************/ + +static void +deactivate (NMModem *self, NMDevice *device) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + + priv->pin_tries = 0; + + if (priv->call) { + dbus_g_proxy_cancel_call (priv->proxy, priv->call); + priv->call = NULL; + } + + if (priv->enable_delay_id) { + g_source_remove (priv->enable_delay_id); + priv->enable_delay_id = 0; + } + + /* Chain up parent */ + NM_MODEM_CLASS (nm_modem_old_parent_class)->deactivate (self, device); +} + +/*****************************************************************************/ + +static void +modem_properties_changed (DBusGProxy *proxy, + const char *interface, + GHashTable *props, + gpointer user_data) +{ + NMModemOld *self = NM_MODEM_OLD (user_data); + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (self); + GValue *value; + gboolean update_state = FALSE; + + if (strcmp (interface, MM_OLD_DBUS_INTERFACE_MODEM) && + strcmp (interface, MM_OLD_DBUS_INTERFACE_MODEM_GSM_CARD)) + return; + + value = g_hash_table_lookup (props, "IpMethod"); + if (value && G_VALUE_HOLDS_UINT (value)) { + g_object_set (self, + NM_MODEM_IP_METHOD, g_value_get_uint (value), + NULL); + } + + value = g_hash_table_lookup (props, "SimIdentifier"); + if (value && G_VALUE_HOLDS_STRING (value)) { + const char *sim_id = g_value_get_string (value); + + g_object_set (self, + NM_MODEM_SIM_ID, (sim_id && *sim_id) ? sim_id : NULL, + NULL); + } + + value = g_hash_table_lookup (props, "UnlockRequired"); + if (value && G_VALUE_HOLDS_STRING (value)) { + g_free (priv->unlock_required); + priv->unlock_required = g_value_dup_string (value); + update_state = TRUE; + } + + value = g_hash_table_lookup (props, "State"); + if (value && G_VALUE_HOLDS_UINT (value)) { + priv->state = g_value_get_uint (value); + update_state = TRUE; + } + + if (update_state) { + nm_modem_set_state (NM_MODEM (self), + mm_state_to_nm (priv->state, priv->unlock_required), + NULL); + } +} + +/*****************************************************************************/ + +static gboolean +check_connection_compatible (NMModem *modem, NMConnection *connection) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (modem); + NMSettingConnection *s_con; + gboolean valid_cdma = FALSE, valid_gsm = FALSE; + const char *ctype; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + ctype = nm_setting_connection_get_connection_type (s_con); + g_assert (ctype); + + /* Check for valid CDMA first */ + if (strcmp (ctype, NM_SETTING_CDMA_SETTING_NAME) == 0) + valid_cdma = !!nm_connection_get_setting_cdma (connection); + + if (strcmp (ctype, NM_SETTING_GSM_SETTING_NAME) == 0) + valid_gsm = !!nm_connection_get_setting_gsm (connection); + + /* Validate CDMA */ + if (priv->caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) { + if (valid_cdma) + return TRUE; + + /* If the modem is only CDMA and the connection is not CDMA, error */ + if ((priv->caps ^ NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) == 0) + return FALSE; + } + + /* Validate 3GPP */ + if (priv->caps & CAPS_3GPP) + return valid_gsm; + + return FALSE; +} + +/*****************************************************************************/ + +static void +complete_ppp_setting (NMConnection *connection) +{ + NMSettingPPP *s_ppp; + + s_ppp = nm_connection_get_setting_ppp (connection); + if (!s_ppp) { + s_ppp = (NMSettingPPP *) nm_setting_ppp_new (); + g_object_set (G_OBJECT (s_ppp), + NM_SETTING_PPP_LCP_ECHO_FAILURE, 5, + NM_SETTING_PPP_LCP_ECHO_INTERVAL, 30, + NULL); + nm_connection_add_setting (connection, NM_SETTING (s_ppp)); + } +} + +static gboolean +complete_connection_3gpp (NMConnection *connection, + const GSList *existing_connections, + GError **error) +{ + NMSettingGsm *s_gsm; + + s_gsm = nm_connection_get_setting_gsm (connection); + if (!s_gsm || !nm_setting_gsm_get_apn (s_gsm)) { + /* Need an APN at least */ + g_set_error_literal (error, + NM_SETTING_GSM_ERROR, + NM_SETTING_GSM_ERROR_MISSING_PROPERTY, + NM_SETTING_GSM_APN); + return FALSE; + } + + if (!nm_setting_gsm_get_number (s_gsm)) + g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL); + + complete_ppp_setting (connection); + + nm_utils_complete_generic (connection, + NM_SETTING_GSM_SETTING_NAME, + existing_connections, + _("GSM connection %d"), + NULL, + FALSE); /* No IPv6 yet by default */ + return TRUE; +} + +static gboolean +complete_connection_cdma (NMConnection *connection, + const GSList *existing_connections, + GError **error) +{ + NMSettingCdma *s_cdma; + + s_cdma = nm_connection_get_setting_cdma (connection); + if (!s_cdma) { + s_cdma = (NMSettingCdma *) nm_setting_cdma_new (); + nm_connection_add_setting (connection, NM_SETTING (s_cdma)); + } + + if (!nm_setting_cdma_get_number (s_cdma)) + g_object_set (G_OBJECT (s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL); + + complete_ppp_setting (connection); + + nm_utils_complete_generic (connection, + NM_SETTING_CDMA_SETTING_NAME, + existing_connections, + _("CDMA connection %d"), + NULL, + FALSE); /* No IPv6 yet by default */ + return TRUE; +} + +static gboolean +complete_connection (NMModem *modem, + NMConnection *connection, + const GSList *existing_connections, + GError **error) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (modem); + + /* If the modem has LTE, complete as 3GPP */ + if (priv->caps & NM_DEVICE_MODEM_CAPABILITY_LTE) + return complete_connection_3gpp (connection, existing_connections, error); + + /* Otherwise, prefer CDMA on the theory that if the modem has CDMA/EVDO + * that's most likely what the user will be using. + */ + if (priv->caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) + return complete_connection_cdma (connection, existing_connections, error); + + if (priv->caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) + return complete_connection_3gpp (connection, existing_connections, error); + + g_set_error_literal (error, NM_MODEM_ERROR, NM_MODEM_ERROR_CONNECTION_INCOMPATIBLE, + "Modem had no WWAN capabilities."); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +get_user_pass (NMModem *modem, + NMConnection *connection, + const char **user, + const char **pass) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (modem); + NMSettingCdma *s_cdma; + NMSettingGsm *s_gsm; + + if (priv->caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) { + s_cdma = nm_connection_get_setting_cdma (connection); + if (s_cdma) { + if (user) + *user = nm_setting_cdma_get_username (s_cdma); + if (pass) + *pass = nm_setting_cdma_get_password (s_cdma); + return TRUE; + } + } + + /* Fall back to GSM; will be used for CDMA devices on LTE networks too */ + s_gsm = nm_connection_get_setting_gsm (connection); + if (s_gsm) { + if (user) + *user = nm_setting_gsm_get_username (s_gsm); + if (pass) + *pass = nm_setting_gsm_get_password (s_gsm); + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +static void +get_capabilities (NMModem *_self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + NMModemOld *self = NM_MODEM_OLD (_self); + + *current_caps = *modem_caps = NM_MODEM_OLD_GET_PRIVATE (self)->caps; +} + +/*****************************************************************************/ + +NMModem * +nm_modem_old_new (const char *path, GHashTable *properties, GError **error) +{ + NMDeviceModemCapabilities caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + NMModemOld *self; + GHashTableIter iter; + const char *prop; + GValue *value; + const char *data_device = NULL; + const char *driver = NULL; + const char *master_device = NULL; + const char *unlock_required = NULL; + const char *device_id = NULL; + guint32 modem_type = MM_OLD_MODEM_TYPE_UNKNOWN; + guint32 ip_method = MM_MODEM_IP_METHOD_PPP; + guint32 ip_timeout = 0; + MMOldModemState state = MM_OLD_MODEM_STATE_UNKNOWN; + + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (properties != NULL, NULL); + + g_hash_table_iter_init (&iter, properties); + while (g_hash_table_iter_next (&iter, (gpointer) &prop, (gpointer) &value)) { + if (g_strcmp0 (prop, "Type") == 0) + modem_type = g_value_get_uint (value); + else if (g_strcmp0 (prop, "MasterDevice") == 0) + master_device = g_value_get_string (value); + else if (g_strcmp0 (prop, "IpMethod") == 0) + ip_method = g_value_get_uint (value); + else if (g_strcmp0 (prop, "Device") == 0) + data_device = g_value_get_string (value); + else if (g_strcmp0 (prop, "Driver") == 0) + driver = g_value_get_string (value); + else if (g_strcmp0 (prop, "IpTimeout") == 0) + ip_timeout = g_value_get_uint (value); + else if (g_strcmp0 (prop, "State") == 0) + state = g_value_get_uint (value); + else if (g_strcmp0 (prop, "UnlockRequired") == 0) + unlock_required = g_value_get_string (value); + else if (g_strcmp0 (prop, "DeviceIdentifier") == 0) + device_id = g_value_get_string (value); + } + + if (modem_type == MM_OLD_MODEM_TYPE_UNKNOWN) { + g_set_error (error, NM_MODEM_ERROR, NM_MODEM_ERROR_INITIALIZATION_FAILED, + "Unhandled modem type %d", modem_type); + return NULL; + } + + if (!master_device || !strlen (master_device)) { + g_set_error_literal (error, NM_MODEM_ERROR, NM_MODEM_ERROR_INITIALIZATION_FAILED, + "Failed to retrieve modem master device."); + return NULL; + } + + if (!driver || !strlen (driver)) { + g_set_error_literal (error, NM_MODEM_ERROR, NM_MODEM_ERROR_INITIALIZATION_FAILED, + "Failed to retrieve modem driver."); + return NULL; + } + + if (!data_device || !strlen (data_device)) { + g_set_error_literal (error, NM_MODEM_ERROR, NM_MODEM_ERROR_INITIALIZATION_FAILED, + "Failed to retrieve modem data device."); + return NULL; + } + + self = (NMModemOld *) g_object_new (NM_TYPE_MODEM_OLD, + NM_MODEM_PATH, path, + NM_MODEM_DRIVER, driver, + NM_MODEM_UID, data_device, + NM_MODEM_CONTROL_PORT, NULL, + NM_MODEM_DATA_PORT, data_device, + NM_MODEM_IP_METHOD, ip_method, + NM_MODEM_IP_TIMEOUT, ip_timeout, + NM_MODEM_DEVICE_ID, device_id, + NM_MODEM_STATE, mm_state_to_nm (state, unlock_required), + NULL); + if (self) { + if (modem_type == MM_OLD_MODEM_TYPE_CDMA) + caps |= NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO; + if (modem_type == MM_OLD_MODEM_TYPE_GSM) + caps |= NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; + + NM_MODEM_OLD_GET_PRIVATE (self)->caps = caps; + NM_MODEM_OLD_GET_PRIVATE (self)->state = state; + NM_MODEM_OLD_GET_PRIVATE (self)->unlock_required = g_strdup (unlock_required); + } + + return (NMModem *) self; +} + +static void +nm_modem_old_init (NMModemOld *self) +{ +} + +static void +get_sim_id_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) +{ + NMModemOld *self = NM_MODEM_OLD (user_data); + GValue value = G_VALUE_INIT; + + if (dbus_g_proxy_end_call (proxy, call_id, NULL, G_TYPE_VALUE, &value, G_TYPE_INVALID)) { + if (G_VALUE_HOLDS_STRING (&value)) { + const char *sim_id = g_value_get_string (&value); + + if (sim_id && *sim_id) + g_object_set (G_OBJECT (self), NM_MODEM_SIM_ID, sim_id, NULL); + } + g_value_unset (&value); + } +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMModemOldPrivate *priv; + DBusGConnection *bus; + + object = G_OBJECT_CLASS (nm_modem_old_parent_class)->constructor (type, n_construct_params, construct_params); + if (!object) + return NULL; + + priv = NM_MODEM_OLD_GET_PRIVATE (object); + + bus = nm_dbus_manager_get_connection (nm_dbus_manager_get ()); + priv->proxy = dbus_g_proxy_new_for_name (bus, + MM_OLD_DBUS_SERVICE, + nm_modem_get_path (NM_MODEM (object)), + MM_OLD_DBUS_INTERFACE_MODEM); + + priv->props_proxy = dbus_g_proxy_new_for_name (bus, + MM_OLD_DBUS_SERVICE, + nm_modem_get_path (NM_MODEM (object)), + DBUS_INTERFACE_PROPERTIES); + dbus_g_object_register_marshaller (g_cclosure_marshal_generic, + G_TYPE_NONE, + G_TYPE_STRING, DBUS_TYPE_G_MAP_OF_VARIANT, + G_TYPE_INVALID); + dbus_g_proxy_add_signal (priv->props_proxy, "MmPropertiesChanged", + G_TYPE_STRING, DBUS_TYPE_G_MAP_OF_VARIANT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->props_proxy, "MmPropertiesChanged", + G_CALLBACK (modem_properties_changed), + object, + NULL); + + /* Request the SIM ID */ + dbus_g_proxy_begin_call (priv->props_proxy, + "Get", + get_sim_id_done, + g_object_ref (object), g_object_unref, + G_TYPE_STRING, MM_OLD_DBUS_INTERFACE_MODEM_GSM_CARD, + G_TYPE_STRING, "SimIdentifier", + G_TYPE_INVALID); + + return object; +} + +static void +dispose (GObject *object) +{ + NMModemOldPrivate *priv = NM_MODEM_OLD_GET_PRIVATE (object); + + if (priv->proxy) { + g_object_unref (priv->proxy); + priv->proxy = NULL; + } + + if (priv->props_proxy) { + g_object_unref (priv->props_proxy); + priv->props_proxy = NULL; + } + + if (priv->connect_properties) { + g_hash_table_destroy (priv->connect_properties); + priv->connect_properties = NULL; + } + + if (priv->enable_delay_id) { + g_source_remove (priv->enable_delay_id); + priv->enable_delay_id = 0; + } + + g_free (priv->unlock_required); + priv->unlock_required = NULL; + + G_OBJECT_CLASS (nm_modem_old_parent_class)->dispose (object); +} + +static void +nm_modem_old_class_init (NMModemOldClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMModemClass *modem_class = NM_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMModemOldPrivate)); + + /* Virtual methods */ + object_class->constructor = constructor; + object_class->dispose = dispose; + + modem_class->get_capabilities = get_capabilities; + modem_class->get_user_pass = get_user_pass; + modem_class->complete_connection = complete_connection; + modem_class->check_connection_compatible = check_connection_compatible; + modem_class->act_stage1_prepare = act_stage1_prepare; + modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start; + modem_class->disconnect = disconnect; + modem_class->deactivate = deactivate; + modem_class->set_mm_enabled = set_mm_enabled; +} diff --git a/src/devices/wwan/nm-modem-old.h b/src/devices/wwan/nm-modem-old.h new file mode 100644 index 000000000..65e3db2d1 --- /dev/null +++ b/src/devices/wwan/nm-modem-old.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef NM_MODEM_OLD_H +#define NM_MODEM_OLD_H + +#include <dbus/dbus-glib.h> +#include <glib-object.h> +#include "nm-modem.h" +#include "nm-modem-old-types.h" + +G_BEGIN_DECLS + +#define NM_TYPE_MODEM_OLD (nm_modem_old_get_type ()) +#define NM_MODEM_OLD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_OLD, NMModemOld)) +#define NM_MODEM_OLD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_MODEM_OLD, NMModemOldClass)) +#define NM_IS_MODEM_OLD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_MODEM_OLD)) +#define NM_IS_MODEM_OLD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_MODEM_OLD)) +#define NM_MODEM_OLD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_MODEM_OLD, NMModemOldClass)) + +typedef struct { + NMModem parent; +} NMModemOld; + +typedef struct { + NMModemClass parent; +} NMModemOldClass; + +GType nm_modem_old_get_type (void); + +NMModem *nm_modem_old_new (const char *path, GHashTable *properties, GError **error); + +G_END_DECLS + +#endif /* NM_MODEM_OLD_H */ diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c new file mode 100644 index 000000000..fd3b4a369 --- /dev/null +++ b/src/devices/wwan/nm-modem.c @@ -0,0 +1,1119 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#include <string.h> +#include "nm-modem.h" +#include "nm-platform.h" +#include "nm-dbus-manager.h" +#include "nm-setting-connection.h" +#include "nm-properties-changed-signal.h" +#include "nm-logging.h" +#include "NetworkManagerUtils.h" +#include "nm-device-private.h" +#include "nm-dbus-glib-types.h" +#include "nm-modem-enum-types.h" + +G_DEFINE_TYPE (NMModem, nm_modem, G_TYPE_OBJECT) + +#define NM_MODEM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_MODEM, NMModemPrivate)) + +enum { + PROP_0, + PROP_CONTROL_PORT, + PROP_DATA_PORT, + PROP_PATH, + PROP_UID, + PROP_DRIVER, + PROP_IP_METHOD, + PROP_IP_TIMEOUT, + PROP_STATE, + PROP_DEVICE_ID, + PROP_SIM_ID, + + LAST_PROP +}; + +typedef struct { + char *uid; + char *path; + char *driver; + char *control_port; + char *data_port; + char *ppp_iface; + guint32 ip_method; + NMModemState state; + NMModemState prev_state; /* revert to this state if enable/disable fails */ + char *device_id; + char *sim_id; + + NMPPPManager *ppp_manager; + + NMActRequest *act_request; + guint32 secrets_tries; + guint32 secrets_id; + + guint32 mm_ip_timeout; + + /* PPP stats */ + guint32 in_bytes; + guint32 out_bytes; +} NMModemPrivate; + +enum { + PPP_STATS, + PPP_FAILED, + PREPARE_RESULT, + IP4_CONFIG_RESULT, + AUTH_REQUESTED, + AUTH_RESULT, + REMOVED, + STATE_CHANGED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + + +/*****************************************************************************/ + +GQuark +nm_modem_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-modem-error"); + return quark; +} + +/*****************************************************************************/ +/* State/enabled/connected */ + +static const char *state_table[] = { + [NM_MODEM_STATE_UNKNOWN] = "unknown", + [NM_MODEM_STATE_FAILED] = "failed", + [NM_MODEM_STATE_INITIALIZING] = "initializing", + [NM_MODEM_STATE_LOCKED] = "locked", + [NM_MODEM_STATE_DISABLED] = "disabled", + [NM_MODEM_STATE_DISABLING] = "disabling", + [NM_MODEM_STATE_ENABLING] = "enabling", + [NM_MODEM_STATE_ENABLED] = "enabled", + [NM_MODEM_STATE_SEARCHING] = "searching", + [NM_MODEM_STATE_REGISTERED] = "registered", + [NM_MODEM_STATE_DISCONNECTING] = "disconnecting", + [NM_MODEM_STATE_CONNECTING] = "connecting", + [NM_MODEM_STATE_CONNECTED] = "connected", +}; + +const char * +nm_modem_state_to_string (NMModemState state) +{ + if (state >= 0 && state < G_N_ELEMENTS (state_table)) + return state_table[state]; + return NULL; +} + +NMModemState +nm_modem_get_state (NMModem *self) +{ + return NM_MODEM_GET_PRIVATE (self)->state; +} + +void +nm_modem_set_state (NMModem *self, + NMModemState new_state, + const char *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + NMModemState old_state = priv->state; + + priv->prev_state = NM_MODEM_STATE_UNKNOWN; + + if (new_state != old_state) { + nm_log_info (LOGD_MB, "(%s): modem state changed, '%s' --> '%s' (reason: %s)\n", + nm_modem_get_uid (self), + nm_modem_state_to_string (old_state), + nm_modem_state_to_string (new_state), + reason ? reason : "none"); + + priv->state = new_state; + g_object_notify (G_OBJECT (self), NM_MODEM_STATE); + g_signal_emit (self, signals[STATE_CHANGED], 0, new_state, old_state, reason); + } +} + +void +nm_modem_set_prev_state (NMModem *self, const char *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + + /* Reset modem to previous state if the state hasn't already changed */ + if (priv->prev_state != NM_MODEM_STATE_UNKNOWN) + nm_modem_set_state (self, priv->prev_state, reason); +} + +void +nm_modem_set_mm_enabled (NMModem *self, + gboolean enabled) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + NMModemState prev_state = priv->state; + + if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) { + nm_log_dbg (LOGD_MB, "(%s) cannot enable modem: already enabled", + nm_modem_get_uid (self)); + return; + } + if (!enabled && priv->state <= NM_MODEM_STATE_DISABLING) { + nm_log_dbg (LOGD_MB, "(%s) cannot disable modem: already disabled", + nm_modem_get_uid (self)); + return; + } + + if (priv->state <= NM_MODEM_STATE_INITIALIZING) { + nm_log_dbg (LOGD_MB, "(%s) cannot enable/disable modem: initializing or failed", + nm_modem_get_uid (self)); + return; + } else if (priv->state == NM_MODEM_STATE_LOCKED) { + /* Don't try to enable if the modem is locked since that will fail */ + nm_log_warn (LOGD_MB, "(%s) cannot enable/disable modem: locked", + nm_modem_get_uid (self)); + + /* Try to unlock the modem if it's being enabled */ + if (enabled) + g_signal_emit_by_name (self, NM_MODEM_AUTH_REQUESTED, 0); + return; + } + + NM_MODEM_GET_CLASS (self)->set_mm_enabled (self, enabled); + + /* Pre-empt the state change signal */ + nm_modem_set_state (self, + enabled ? NM_MODEM_STATE_ENABLING : NM_MODEM_STATE_DISABLING, + "user preference"); + priv->prev_state = prev_state; +} + +void +nm_modem_emit_removed (NMModem *self) +{ + g_signal_emit (self, signals[REMOVED], 0); +} + +/*****************************************************************************/ +/* IP method PPP */ + +static void +ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + g_signal_emit (NM_MODEM (user_data), signals[PPP_FAILED], 0, NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + g_signal_emit (NM_MODEM (user_data), signals[PPP_FAILED], 0, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ip4_config (NMPPPManager *ppp_manager, + const char *iface, + NMIP4Config *config, + gpointer user_data) +{ + NMModem *self = NM_MODEM (user_data); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + guint32 i, num; + guint32 bad_dns1 = htonl (0x0A0B0C0D); + guint32 good_dns1 = htonl (0x04020201); /* GTE nameserver */ + guint32 bad_dns2 = htonl (0x0A0B0C0E); + guint32 good_dns2 = htonl (0x04020202); /* GTE nameserver */ + gboolean dns_workaround = FALSE; + + /* Notify about the new data port to use */ + g_free (priv->ppp_iface); + priv->ppp_iface = g_strdup (iface); + g_object_notify (G_OBJECT (self), NM_MODEM_DATA_PORT); + + /* Work around a PPP bug (#1732) which causes many mobile broadband + * providers to return 10.11.12.13 and 10.11.12.14 for the DNS servers. + * Apparently fixed in ppp-2.4.5 but we've had some reports that this is + * not the case. + * + * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=2e09ef6886bbf00bc5a9a641110f801e372ffde6 + * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=f8191bf07df374f119a07910a79217c7618f113e + */ + + num = nm_ip4_config_get_num_nameservers (config); + if (num == 2) { + gboolean found1 = FALSE, found2 = FALSE; + + for (i = 0; i < num; i++) { + guint32 ns = nm_ip4_config_get_nameserver (config, i); + + if (ns == bad_dns1) + found1 = TRUE; + else if (ns == bad_dns2) + found2 = TRUE; + } + + /* Be somewhat conservative about substitutions; the "bad" nameservers + * could actually be valid in some cases, so only substitute if ppp + * returns *only* the two bad nameservers. + */ + dns_workaround = (found1 && found2); + } + + if (!num || dns_workaround) { + nm_log_warn (LOGD_PPP, "compensating for invalid PPP-provided nameservers"); + nm_ip4_config_reset_nameservers (config); + nm_ip4_config_add_nameserver (config, good_dns1); + nm_ip4_config_add_nameserver (config, good_dns2); + } + + g_signal_emit (self, signals[IP4_CONFIG_RESULT], 0, config, NULL); +} + +static void +ppp_stats (NMPPPManager *ppp_manager, + guint32 in_bytes, + guint32 out_bytes, + gpointer user_data) +{ + NMModem *self = NM_MODEM (user_data); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + + if (priv->in_bytes != in_bytes || priv->out_bytes != out_bytes) { + priv->in_bytes = in_bytes; + priv->out_bytes = out_bytes; + + g_signal_emit (self, signals[PPP_STATS], 0, in_bytes, out_bytes); + } +} + +static NMActStageReturn +ppp_stage3_ip4_config_start (NMModem *self, + NMActRequest *req, + NMDeviceStateReason *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + const char *ppp_name = NULL; + GError *error = NULL; + NMActStageReturn ret; + guint ip_timeout = 20; + + g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (NM_IS_ACT_REQUEST (req), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + if (NM_MODEM_GET_CLASS (self)->get_user_pass) { + NMConnection *connection = nm_act_request_get_connection (req); + + g_assert (connection); + if (!NM_MODEM_GET_CLASS (self)->get_user_pass (self, connection, &ppp_name, NULL)) + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* Check if ModemManager requested a specific IP timeout to be used. If 0 reported, + * use the default one (20s) */ + if (priv->mm_ip_timeout > 0) { + nm_log_info (LOGD_PPP, "using modem-specified IP timeout: %u seconds", + priv->mm_ip_timeout); + ip_timeout = priv->mm_ip_timeout; + } + + priv->ppp_manager = nm_ppp_manager_new (priv->data_port); + if (nm_ppp_manager_start (priv->ppp_manager, req, ppp_name, ip_timeout, &error)) { + g_signal_connect (priv->ppp_manager, "state-changed", + G_CALLBACK (ppp_state_changed), + self); + g_signal_connect (priv->ppp_manager, "ip4-config", + G_CALLBACK (ppp_ip4_config), + self); + g_signal_connect (priv->ppp_manager, "stats", + G_CALLBACK (ppp_stats), + self); + + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else { + nm_log_err (LOGD_PPP, "error starting PPP: (%d) %s", + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_error_free (error); + + g_object_unref (priv->ppp_manager); + priv->ppp_manager = NULL; + + *reason = NM_DEVICE_STATE_REASON_PPP_START_FAILED; + ret = NM_ACT_STAGE_RETURN_FAILURE; + } + + return ret; +} + +/*****************************************************************************/ + +NMActStageReturn +nm_modem_stage3_ip4_config_start (NMModem *self, + NMDevice *device, + NMDeviceClass *device_class, + NMDeviceStateReason *reason) +{ + NMModemPrivate *priv; + NMActRequest *req; + NMActStageReturn ret; + + g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (NM_IS_DEVICE (device), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (NM_IS_DEVICE_CLASS (device_class), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + req = nm_device_get_act_request (device); + g_assert (req); + + priv = NM_MODEM_GET_PRIVATE (self); + switch (priv->ip_method) { + case MM_MODEM_IP_METHOD_PPP: + ret = ppp_stage3_ip4_config_start (self, req, reason); + break; + case MM_MODEM_IP_METHOD_STATIC: + ret = NM_MODEM_GET_CLASS (self)->static_stage3_ip4_config_start (self, req, reason); + break; + case MM_MODEM_IP_METHOD_DHCP: + ret = device_class->act_stage3_ip4_config_start (device, NULL, reason); + break; + default: + nm_log_err (LOGD_MB, "unknown IP method %d", priv->ip_method); + ret = NM_ACT_STAGE_RETURN_FAILURE; + break; + } + + return ret; +} + +void +nm_modem_ip4_pre_commit (NMModem *modem, + NMDevice *device, + NMIP4Config *config) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (modem); + + /* If the modem has an ethernet-type data interface (ie, not PPP and thus + * not point-to-point) and IP config has a /32 prefix, then we assume that + * ARP will be pointless and we turn it off. + */ + if ( priv->ip_method == MM_MODEM_IP_METHOD_STATIC + || priv->ip_method == MM_MODEM_IP_METHOD_DHCP) { + const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, 0); + + g_assert (address); + if (address->plen == 32) + nm_platform_link_set_noarp (nm_device_get_ip_ifindex (device)); + } +} + +/*****************************************************************************/ + +NMActStageReturn +nm_modem_stage3_ip6_config_start (NMModem *self, + NMDevice *device, + NMDeviceClass *device_class, + NMDeviceStateReason *reason) +{ + /* FIXME: We don't support IPv6 on modems quite yet... */ + nm_device_activate_schedule_ip6_config_timeout (device); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static void +cancel_get_secrets (NMModem *self) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + + if (priv->secrets_id) { + nm_act_request_cancel_secrets (priv->act_request, priv->secrets_id); + priv->secrets_id = 0; + } +} + +static void +modem_secrets_cb (NMActRequest *req, + guint32 call_id, + NMConnection *connection, + GError *error, + gpointer user_data) +{ + NMModem *self = NM_MODEM (user_data); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + + g_return_if_fail (call_id == priv->secrets_id); + + priv->secrets_id = 0; + + if (error) + nm_log_warn (LOGD_MB, "%s", error->message); + + g_signal_emit (self, signals[AUTH_RESULT], 0, error); +} + +gboolean +nm_modem_get_secrets (NMModem *self, + const char *setting_name, + gboolean request_new, + const char *hint) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + NMSettingsGetSecretsFlags flags = NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + cancel_get_secrets (self); + + if (request_new) + flags |= NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW; + priv->secrets_id = nm_act_request_get_secrets (priv->act_request, + setting_name, + flags, + hint, + modem_secrets_cb, + self); + if (priv->secrets_id) + g_signal_emit (self, signals[AUTH_REQUESTED], 0); + + return !!(priv->secrets_id); +} + +/*****************************************************************************/ + +static NMActStageReturn +act_stage1_prepare (NMModem *modem, + NMConnection *connection, + NMDeviceStateReason *reason) +{ + *reason = NM_DEVICE_STATE_REASON_UNKNOWN; + return NM_ACT_STAGE_RETURN_FAILURE; +} + +NMActStageReturn +nm_modem_act_stage1_prepare (NMModem *self, + NMActRequest *req, + NMDeviceStateReason *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + NMActStageReturn ret; + GPtrArray *hints = NULL; + const char *setting_name = NULL; + NMSettingsGetSecretsFlags flags = NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION; + NMConnection *connection; + + if (priv->act_request) + g_object_unref (priv->act_request); + priv->act_request = g_object_ref (req); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + setting_name = nm_connection_need_secrets (connection, &hints); + if (!setting_name) { + /* Ready to connect */ + g_assert (!hints); + return NM_MODEM_GET_CLASS (self)->act_stage1_prepare (self, connection, reason); + } + + /* Secrets required... */ + if (priv->secrets_tries++) + flags |= NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW; + + priv->secrets_id = nm_act_request_get_secrets (req, + setting_name, + flags, + hints ? g_ptr_array_index (hints, 0) : NULL, + modem_secrets_cb, + self); + if (priv->secrets_id) { + g_signal_emit (self, signals[AUTH_REQUESTED], 0); + ret = NM_ACT_STAGE_RETURN_POSTPONE; + } else { + *reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + ret = NM_ACT_STAGE_RETURN_FAILURE; + } + + if (hints) + g_ptr_array_free (hints, TRUE); + + return ret; +} + +/*****************************************************************************/ + +NMActStageReturn +nm_modem_act_stage2_config (NMModem *self, + NMActRequest *req, + NMDeviceStateReason *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + + /* Clear secrets tries counter since secrets were successfully used + * already if we get here. + */ + priv->secrets_tries = 0; + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +/*****************************************************************************/ + +gboolean +nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection) +{ + if (NM_MODEM_GET_CLASS (self)->check_connection_compatible) + return NM_MODEM_GET_CLASS (self)->check_connection_compatible (self, connection); + return FALSE; +} + +/*****************************************************************************/ + +gboolean +nm_modem_complete_connection (NMModem *self, + NMConnection *connection, + const GSList *existing_connections, + GError **error) +{ + if (NM_MODEM_GET_CLASS (self)->complete_connection) + return NM_MODEM_GET_CLASS (self)->complete_connection (self, connection, existing_connections, error); + return FALSE; +} + +/*****************************************************************************/ + +static void +deactivate (NMModem *self, NMDevice *device) +{ + NMModemPrivate *priv; + int ifindex; + + g_return_if_fail (NM_IS_MODEM (self)); + g_return_if_fail (NM_IS_DEVICE (device)); + + priv = NM_MODEM_GET_PRIVATE (self); + + priv->secrets_tries = 0; + + if (priv->act_request) { + cancel_get_secrets (self); + g_object_unref (priv->act_request); + priv->act_request = NULL; + } + + priv->in_bytes = priv->out_bytes = 0; + + if (priv->ppp_manager) { + g_object_unref (priv->ppp_manager); + priv->ppp_manager = NULL; + } + + switch (priv->ip_method) { + case MM_MODEM_IP_METHOD_PPP: + break; + case MM_MODEM_IP_METHOD_STATIC: + case MM_MODEM_IP_METHOD_DHCP: + ifindex = nm_device_get_ip_ifindex (device); + if (ifindex > 0) { + nm_platform_route_flush (ifindex); + nm_platform_address_flush (ifindex); + nm_platform_link_set_down (ifindex); + } + break; + default: + nm_log_err (LOGD_MB, "unknown IP method %d", priv->ip_method); + break; + } + + g_free (priv->ppp_iface); + priv->ppp_iface = NULL; +} + +/*****************************************************************************/ + +void +nm_modem_deactivate (NMModem *self, NMDevice *device) +{ + NM_MODEM_GET_CLASS (self)->deactivate (self, device); +} + +/*****************************************************************************/ + +void +nm_modem_device_state_changed (NMModem *self, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + gboolean was_connected = FALSE, warn = TRUE; + NMModemPrivate *priv; + + g_return_if_fail (NM_IS_MODEM (self)); + + if (old_state >= NM_DEVICE_STATE_PREPARE && old_state <= NM_DEVICE_STATE_DEACTIVATING) + was_connected = TRUE; + + priv = NM_MODEM_GET_PRIVATE (self); + + /* Make sure we don't leave the serial device open */ + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + case NM_DEVICE_STATE_DISCONNECTED: + case NM_DEVICE_STATE_FAILED: + if (priv->act_request) { + cancel_get_secrets (self); + g_object_unref (priv->act_request); + priv->act_request = NULL; + } + + if (was_connected) { + /* Don't bother warning on FAILED since the modem is already gone */ + if (new_state == NM_DEVICE_STATE_FAILED) + warn = FALSE; + NM_MODEM_GET_CLASS (self)->disconnect (self, warn); + } + break; + default: + break; + } +} + +/*****************************************************************************/ + +const char * +nm_modem_get_uid (NMModem *self) +{ + g_return_val_if_fail (NM_IS_MODEM (self), NULL); + + return NM_MODEM_GET_PRIVATE (self)->uid; +} + +const char * +nm_modem_get_path (NMModem *self) +{ + g_return_val_if_fail (NM_IS_MODEM (self), NULL); + + return NM_MODEM_GET_PRIVATE (self)->path; +} + +const char * +nm_modem_get_driver (NMModem *self) +{ + g_return_val_if_fail (NM_IS_MODEM (self), NULL); + + return NM_MODEM_GET_PRIVATE (self)->driver; +} + +const char * +nm_modem_get_control_port (NMModem *self) +{ + g_return_val_if_fail (NM_IS_MODEM (self), NULL); + + return NM_MODEM_GET_PRIVATE (self)->control_port; +} + +const char * +nm_modem_get_data_port (NMModem *self) +{ + g_return_val_if_fail (NM_IS_MODEM (self), NULL); + + /* The ppp_iface takes precedence over the data interface when PPP is used, + * since data_iface is the TTY over which PPP is run, and that TTY can't + * do IP. The caller really wants the thing that's doing IP. + */ + return NM_MODEM_GET_PRIVATE (self)->ppp_iface ? + NM_MODEM_GET_PRIVATE (self)->ppp_iface : NM_MODEM_GET_PRIVATE (self)->data_port; +} + +gboolean +nm_modem_owns_port (NMModem *self, const char *iface) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + + g_return_val_if_fail (iface != NULL, FALSE); + + if (NM_MODEM_GET_CLASS (self)->owns_port) + return NM_MODEM_GET_CLASS (self)->owns_port (self, iface); + + /* Fall back to data/control ports */ + if (priv->ppp_iface && (strcmp (priv->ppp_iface, iface) == 0)) + return TRUE; + if (priv->data_port && (strcmp (priv->data_port, iface) == 0)) + return TRUE; + if (priv->control_port && (strcmp (priv->control_port, iface) == 0)) + return TRUE; + + return FALSE; +} + +/*****************************************************************************/ + +void +nm_modem_get_capabilities (NMModem *self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + g_return_if_fail (NM_IS_MODEM (self)); + + NM_MODEM_GET_CLASS (self)->get_capabilities (self, modem_caps, current_caps); +} + +/*****************************************************************************/ + +static void +nm_modem_init (NMModem *self) +{ +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMModemPrivate *priv; + + object = G_OBJECT_CLASS (nm_modem_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + priv = NM_MODEM_GET_PRIVATE (object); + + if (!priv->data_port && !priv->control_port) { + nm_log_err (LOGD_HW, "neither modem command nor data interface provided"); + goto err; + } + + if (!priv->path) { + nm_log_err (LOGD_HW, "D-Bus path not provided"); + goto err; + } + + return object; + + err: + g_object_unref (object); + return NULL; +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string (value, priv->path); + break; + case PROP_DRIVER: + g_value_set_string (value, priv->driver); + break; + case PROP_CONTROL_PORT: + g_value_set_string (value, priv->control_port); + break; + case PROP_DATA_PORT: + g_value_set_string (value, nm_modem_get_data_port (NM_MODEM (object))); + break; + case PROP_UID: + g_value_set_string (value, priv->uid); + break; + case PROP_IP_METHOD: + g_value_set_uint (value, priv->ip_method); + break; + case PROP_IP_TIMEOUT: + g_value_set_uint (value, priv->mm_ip_timeout); + break; + case PROP_STATE: + g_value_set_enum (value, priv->state); + break; + case PROP_DEVICE_ID: + g_value_set_string (value, priv->device_id); + break; + case PROP_SIM_ID: + g_value_set_string (value, priv->sim_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_PATH: + /* Construct only */ + priv->path = g_value_dup_string (value); + break; + case PROP_DRIVER: + /* Construct only */ + priv->driver = g_value_dup_string (value); + break; + case PROP_CONTROL_PORT: + priv->control_port = g_value_dup_string (value); + break; + case PROP_DATA_PORT: + priv->data_port = g_value_dup_string (value); + break; + case PROP_UID: + /* Construct only */ + priv->uid = g_value_dup_string (value); + break; + case PROP_IP_METHOD: + priv->ip_method = g_value_get_uint (value); + break; + case PROP_IP_TIMEOUT: + priv->mm_ip_timeout = g_value_get_uint (value); + break; + case PROP_STATE: + priv->state = g_value_get_enum (value); + break; + case PROP_DEVICE_ID: + /* construct only */ + priv->device_id = g_value_dup_string (value); + break; + case PROP_SIM_ID: + g_free (priv->sim_id); + priv->sim_id = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (object); + + if (priv->act_request) { + g_object_unref (priv->act_request); + priv->act_request = NULL; + } + + G_OBJECT_CLASS (nm_modem_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (object); + + g_free (priv->uid); + g_free (priv->path); + g_free (priv->driver); + g_free (priv->control_port); + g_free (priv->data_port); + g_free (priv->device_id); + g_free (priv->sim_id); + + G_OBJECT_CLASS (nm_modem_parent_class)->finalize (object); +} + +static void +nm_modem_class_init (NMModemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMModemPrivate)); + + /* Virtual methods */ + object_class->constructor = constructor; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + klass->act_stage1_prepare = act_stage1_prepare; + klass->deactivate = deactivate; + + /* Properties */ + + g_object_class_install_property + (object_class, PROP_UID, + g_param_spec_string (NM_MODEM_UID, + "UID", + "Modem unique ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_PATH, + g_param_spec_string (NM_MODEM_PATH, + "DBus path", + "DBus path", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_DRIVER, + g_param_spec_string (NM_MODEM_DRIVER, + "Driver", + "Driver", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_CONTROL_PORT, + g_param_spec_string (NM_MODEM_CONTROL_PORT, + "Control port", + "The port controlling the modem", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_DATA_PORT, + g_param_spec_string (NM_MODEM_DATA_PORT, + "Data port", + "The port to connect to", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (object_class, PROP_IP_METHOD, + g_param_spec_uint (NM_MODEM_IP_METHOD, + "IP method", + "IP method", + MM_MODEM_IP_METHOD_PPP, + MM_MODEM_IP_METHOD_DHCP, + MM_MODEM_IP_METHOD_PPP, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_IP_TIMEOUT, + g_param_spec_uint (NM_MODEM_IP_TIMEOUT, + "IP timeout", + "IP timeout", + 0, 360, 20, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_STATE, + g_param_spec_enum (NM_MODEM_STATE, + "State", + "State", + NM_TYPE_MODEM_STATE, + NM_MODEM_STATE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_DEVICE_ID, + g_param_spec_string (NM_MODEM_DEVICE_ID, + "DeviceId", + "Device ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_SIM_ID, + g_param_spec_string (NM_MODEM_SIM_ID, + "SimId", + "Sim ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /* Signals */ + + signals[PPP_STATS] = + g_signal_new ("ppp-stats", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, ppp_stats), + NULL, NULL, NULL, + G_TYPE_NONE, 2, + G_TYPE_UINT, G_TYPE_UINT); + + signals[PPP_FAILED] = + g_signal_new ("ppp-failed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, ppp_failed), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_UINT); + + signals[IP4_CONFIG_RESULT] = + g_signal_new (NM_MODEM_IP4_CONFIG_RESULT, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, ip4_config_result), + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_POINTER); + + signals[PREPARE_RESULT] = + g_signal_new (NM_MODEM_PREPARE_RESULT, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, prepare_result), + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_BOOLEAN, G_TYPE_UINT); + + signals[AUTH_REQUESTED] = + g_signal_new (NM_MODEM_AUTH_REQUESTED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, auth_requested), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[AUTH_RESULT] = + g_signal_new (NM_MODEM_AUTH_RESULT, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, auth_result), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + signals[REMOVED] = + g_signal_new (NM_MODEM_REMOVED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, removed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[STATE_CHANGED] = + g_signal_new (NM_MODEM_STATE_CHANGED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMModemClass, state_changed), + NULL, NULL, NULL, + G_TYPE_NONE, 2, NM_TYPE_MODEM_STATE, NM_TYPE_MODEM_STATE); + + dbus_g_error_domain_register (NM_MODEM_ERROR, + NM_DBUS_INTERFACE_DEVICE_MODEM, + NM_TYPE_MODEM_ERROR); +} diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h new file mode 100644 index 000000000..c992cf721 --- /dev/null +++ b/src/devices/wwan/nm-modem.h @@ -0,0 +1,217 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef NM_MODEM_H +#define NM_MODEM_H + +#include <dbus/dbus-glib.h> +#include <glib-object.h> +#include "ppp-manager/nm-ppp-manager.h" +#include "nm-device.h" + +G_BEGIN_DECLS + +#define NM_TYPE_MODEM (nm_modem_get_type ()) +#define NM_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM, NMModem)) +#define NM_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_MODEM, NMModemClass)) +#define NM_IS_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_MODEM)) +#define NM_IS_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_MODEM)) +#define NM_MODEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_MODEM, NMModemClass)) + +/* Properties */ +#define NM_MODEM_UID "uid" +#define NM_MODEM_PATH "path" +#define NM_MODEM_DRIVER "driver" +#define NM_MODEM_CONTROL_PORT "control-port" +#define NM_MODEM_DATA_PORT "data-port" +#define NM_MODEM_IP_METHOD "ip-method" +#define NM_MODEM_IP_TIMEOUT "ip-timeout" +#define NM_MODEM_STATE "state" +#define NM_MODEM_DEVICE_ID "device-id" +#define NM_MODEM_SIM_ID "sim-id" + +/* Signals */ +#define NM_MODEM_PPP_STATS "ppp-stats" +#define NM_MODEM_PPP_FAILED "ppp-failed" +#define NM_MODEM_PREPARE_RESULT "prepare-result" +#define NM_MODEM_IP4_CONFIG_RESULT "ip4-config-result" +#define NM_MODEM_AUTH_REQUESTED "auth-requested" +#define NM_MODEM_AUTH_RESULT "auth-result" +#define NM_MODEM_REMOVED "removed" +#define NM_MODEM_STATE_CHANGED "state-changed" + +#define MM_MODEM_IP_METHOD_PPP 0 +#define MM_MODEM_IP_METHOD_STATIC 1 +#define MM_MODEM_IP_METHOD_DHCP 2 + +typedef enum { + NM_MODEM_ERROR_CONNECTION_NOT_GSM, /*< nick=ConnectionNotGsm >*/ + NM_MODEM_ERROR_CONNECTION_NOT_CDMA, /*< nick=ConnectionNotCdma >*/ + NM_MODEM_ERROR_CONNECTION_INVALID, /*< nick=ConnectionInvalid >*/ + NM_MODEM_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ + NM_MODEM_ERROR_INITIALIZATION_FAILED, /*< nick=InitializationFailed >*/ +} NMModemError; + +typedef enum { /*< underscore_name=nm_modem_state >*/ + NM_MODEM_STATE_UNKNOWN = 0, + NM_MODEM_STATE_FAILED = 1, + NM_MODEM_STATE_INITIALIZING = 2, + NM_MODEM_STATE_LOCKED = 3, + NM_MODEM_STATE_DISABLED = 4, + NM_MODEM_STATE_DISABLING = 5, + NM_MODEM_STATE_ENABLING = 6, + NM_MODEM_STATE_ENABLED = 7, + NM_MODEM_STATE_SEARCHING = 8, + NM_MODEM_STATE_REGISTERED = 9, + NM_MODEM_STATE_DISCONNECTING = 10, + NM_MODEM_STATE_CONNECTING = 11, + NM_MODEM_STATE_CONNECTED = 12, +} NMModemState; + +#define NM_MODEM_ERROR (nm_modem_error_quark ()) +GQuark nm_modem_error_quark (void); + + +typedef struct { + GObject parent; +} NMModem; + +typedef struct { + GObjectClass parent; + + void (*get_capabilities) (NMModem *self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps); + + gboolean (*get_user_pass) (NMModem *modem, + NMConnection *connection, + const char **user, + const char **pass); + + gboolean (*check_connection_compatible) (NMModem *modem, + NMConnection *connection); + + gboolean (*complete_connection) (NMModem *modem, + NMConnection *connection, + const GSList *existing_connections, + GError **error); + + NMActStageReturn (*act_stage1_prepare) (NMModem *modem, + NMConnection *connection, + NMDeviceStateReason *reason); + + NMActStageReturn (*static_stage3_ip4_config_start) (NMModem *self, + NMActRequest *req, + NMDeviceStateReason *reason); + + void (*set_mm_enabled) (NMModem *self, gboolean enabled); + + void (*disconnect) (NMModem *self, gboolean warn); + + void (*deactivate) (NMModem *self, NMDevice *device); + + gboolean (*owns_port) (NMModem *self, const char *iface); + + /* Signals */ + void (*ppp_stats) (NMModem *self, guint32 in_bytes, guint32 out_bytes); + void (*ppp_failed) (NMModem *self, NMDeviceStateReason reason); + + void (*prepare_result) (NMModem *self, gboolean success, NMDeviceStateReason reason); + void (*ip4_config_result) (NMModem *self, NMIP4Config *config, GError *error); + + void (*auth_requested) (NMModem *self); + void (*auth_result) (NMModem *self, GError *error); + + void (*state_changed) (NMModem *self, + NMModemState new_state, + NMModemState old_state); + + void (*removed) (NMModem *self); +} NMModemClass; + +GType nm_modem_get_type (void); + +const char *nm_modem_get_path (NMModem *modem); +const char *nm_modem_get_uid (NMModem *modem); +const char *nm_modem_get_control_port (NMModem *modem); +const char *nm_modem_get_data_port (NMModem *modem); +const char *nm_modem_get_driver (NMModem *modem); + +gboolean nm_modem_owns_port (NMModem *modem, const char *iface); + +void nm_modem_get_capabilities (NMModem *self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps); + +gboolean nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection); + +gboolean nm_modem_complete_connection (NMModem *self, + NMConnection *connection, + const GSList *existing_connections, + GError **error); + +NMActStageReturn nm_modem_act_stage1_prepare (NMModem *modem, + NMActRequest *req, + NMDeviceStateReason *reason); + +NMActStageReturn nm_modem_act_stage2_config (NMModem *modem, + NMActRequest *req, + NMDeviceStateReason *reason); + +NMActStageReturn nm_modem_stage3_ip4_config_start (NMModem *modem, + NMDevice *device, + NMDeviceClass *device_class, + NMDeviceStateReason *reason); + +NMActStageReturn nm_modem_stage3_ip6_config_start (NMModem *modem, + NMDevice *device, + NMDeviceClass *device_class, + NMDeviceStateReason *reason); + +void nm_modem_ip4_pre_commit (NMModem *modem, NMDevice *device, NMIP4Config *config); + +gboolean nm_modem_get_secrets (NMModem *modem, + const char *setting_name, + gboolean request_new, + const char *hint); + +void nm_modem_deactivate (NMModem *modem, NMDevice *device); + +void nm_modem_device_state_changed (NMModem *modem, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason); + +void nm_modem_set_mm_enabled (NMModem *self, gboolean enabled); + +NMModemState nm_modem_get_state (NMModem *self); +void nm_modem_set_state (NMModem *self, + NMModemState new_state, + const char *reason); +void nm_modem_set_prev_state (NMModem *self, const char *reason); +const char * nm_modem_state_to_string (NMModemState state); + +/* For the modem-manager only */ +void nm_modem_emit_removed (NMModem *self); + +G_END_DECLS + +#endif /* NM_MODEM_H */ diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c new file mode 100644 index 000000000..b1e2307e6 --- /dev/null +++ b/src/devices/wwan/nm-wwan-factory.c @@ -0,0 +1,136 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include <string.h> +#include <gmodule.h> + +#include "config.h" +#include "nm-device-factory.h" +#include "nm-wwan-factory.h" +#include "nm-modem-manager.h" +#include "nm-device-modem.h" +#include "nm-logging.h" + +static GType nm_wwan_factory_get_type (void); + +static void device_factory_interface_init (NMDeviceFactory *factory_iface); + +G_DEFINE_TYPE_EXTENDED (NMWwanFactory, nm_wwan_factory, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_DEVICE_FACTORY, device_factory_interface_init)) + +#define NM_WWAN_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_WWAN_FACTORY, NMWwanFactoryPrivate)) + +typedef struct { + NMModemManager *mm; +} NMWwanFactoryPrivate; + +/************************************************************************/ + +#define PLUGIN_TYPE NM_DEVICE_TYPE_MODEM + +G_MODULE_EXPORT NMDeviceFactory * +nm_device_factory_create (GError **error) +{ + return (NMDeviceFactory *) g_object_new (NM_TYPE_WWAN_FACTORY, NULL); +} + +G_MODULE_EXPORT NMDeviceType +nm_device_factory_get_device_type (void) +{ + return PLUGIN_TYPE; +} + +/************************************************************************/ + +static void +modem_added_cb (NMModemManager *manager, + NMModem *modem, + gpointer user_data) +{ + NMWwanFactory *self = NM_WWAN_FACTORY (user_data); + NMDevice *device; + const char *driver, *port; + + /* Do nothing if the modem was consumed by some other plugin */ + if (nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (self), G_OBJECT (modem))) + return; + + driver = nm_modem_get_driver (modem); + + /* If it was a Bluetooth modem and no bluetooth device claimed it, ignore + * it. The rfcomm port (and thus the modem) gets created automatically + * by the Bluetooth code during the connection process. + */ + if (driver && strstr (driver, "bluetooth")) { + port = nm_modem_get_data_port (modem); + if (!port) + port = nm_modem_get_control_port (modem); + nm_log_info (LOGD_MB, "ignoring modem '%s' (no associated Bluetooth device)", port); + return; + } + + /* Make the new modem device */ + device = nm_device_modem_new (modem); + g_assert (device); + g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); + g_object_unref (device); +} + +static void +nm_wwan_factory_init (NMWwanFactory *self) +{ + NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE (self); + + priv->mm = g_object_new (NM_TYPE_MODEM_MANAGER, NULL); + g_assert (priv->mm); + g_signal_connect (priv->mm, + NM_MODEM_MANAGER_MODEM_ADDED, + G_CALLBACK (modem_added_cb), + self); +} + +static void +device_factory_interface_init (NMDeviceFactory *factory_iface) +{ +} + +static void +dispose (GObject *object) +{ + NMWwanFactory *self = NM_WWAN_FACTORY (object); + NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE (self); + + if (priv->mm) + g_signal_handlers_disconnect_by_func (priv->mm, modem_added_cb, self); + g_clear_object (&priv->mm); + + /* Chain up to the parent class */ + G_OBJECT_CLASS (nm_wwan_factory_parent_class)->dispose (object); +} + +static void +nm_wwan_factory_class_init (NMWwanFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMWwanFactoryPrivate)); + + object_class->dispose = dispose; +} diff --git a/src/devices/wwan/nm-wwan-factory.h b/src/devices/wwan/nm-wwan-factory.h new file mode 100644 index 000000000..b7aee01f1 --- /dev/null +++ b/src/devices/wwan/nm-wwan-factory.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef NM_WWAN_FACTORY_H +#define NM_WWAN_FACTORY_H + +#include <glib-object.h> + +#define NM_TYPE_WWAN_FACTORY (nm_wwan_factory_get_type ()) +#define NM_WWAN_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactory)) + +typedef struct { + GObject parent; +} NMWwanFactory; + +typedef struct { + GObjectClass parent; +} NMWwanFactoryClass; + +#endif /* NM_WWAN_FACTORY_H */ diff --git a/src/devices/wwan/wwan-exports.ver b/src/devices/wwan/wwan-exports.ver new file mode 100644 index 000000000..dc505d176 --- /dev/null +++ b/src/devices/wwan/wwan-exports.ver @@ -0,0 +1,28 @@ +{ +global: + nm_modem_act_stage1_prepare; + nm_modem_act_stage2_config; + nm_modem_check_connection_compatible; + nm_modem_complete_connection; + nm_modem_deactivate; + nm_modem_device_state_changed; + nm_modem_error_quark; + nm_modem_get_capabilities; + nm_modem_get_control_port; + nm_modem_get_data_port; + nm_modem_get_driver; + nm_modem_get_path; + nm_modem_get_secrets; + nm_modem_get_state; + nm_modem_get_type; + nm_modem_get_uid; + nm_modem_ip4_pre_commit; + nm_modem_manager_get_type; + nm_modem_owns_port; + nm_modem_set_mm_enabled; + nm_modem_stage3_ip4_config_start; + nm_modem_stage3_ip6_config_start; + nm_modem_state_to_string; +local: + *; +}; |