@@ -37,6 +37,7 @@ import (
3737 "github.com/apernet/hysteria/app/v2/internal/url"
3838 "github.com/apernet/hysteria/app/v2/internal/utils"
3939 "github.com/apernet/hysteria/core/v2/client"
40+ coreErrs "github.com/apernet/hysteria/core/v2/errors"
4041 "github.com/apernet/hysteria/extras/v2/correctnet"
4142 "github.com/apernet/hysteria/extras/v2/obfs"
4243 "github.com/apernet/hysteria/extras/v2/realm"
@@ -90,6 +91,28 @@ type clientConfig struct {
9091 UDPTProxy * udpTProxyConfig `mapstructure:"udpTProxy"`
9192 TCPRedirect * tcpRedirectConfig `mapstructure:"tcpRedirect"`
9293 TUN * tunConfig `mapstructure:"tun"`
94+ PPP * pppConfig `mapstructure:"ppp"`
95+ }
96+
97+ type pppSSTPConfig struct {
98+ BinaryPath string `mapstructure:"binaryPath"`
99+ Listen string `mapstructure:"listen"`
100+ CertDir string `mapstructure:"certDir"`
101+ Endpoint string `mapstructure:"endpoint"`
102+ User string `mapstructure:"user"`
103+ Password string `mapstructure:"password"`
104+ MSSClamp * int `mapstructure:"mssClamp"` // nil=auto, 0=off, >0=forced
105+ ServerRoute * bool `mapstructure:"serverRoute"`
106+ LogLevel string `mapstructure:"logLevel"`
107+ }
108+
109+ type pppConfig struct {
110+ Mode string `mapstructure:"mode"`
111+ MTU uint32 `mapstructure:"mtu"`
112+ PPPDPath string `mapstructure:"pppdPath"`
113+ PPPDArgs []string `mapstructure:"pppdArgs"`
114+ DataStreams int `mapstructure:"dataStreams"`
115+ SSTP * pppSSTPConfig `mapstructure:"sstp"`
93116}
94117
95118type clientConfigRealm struct {
@@ -618,6 +641,9 @@ func (c *clientConfig) Config() (*client.Config, error) {
618641 return nil , err
619642 }
620643 }
644+ if c .PPP != nil && c .PPP .DataStreams == 0 {
645+ hyConfig .PPPMode = true
646+ }
621647 return hyConfig , nil
622648}
623649
@@ -810,29 +836,75 @@ func runClientCmd(cmd *cobra.Command, args []string) {
810836 runClient (defaultViper )
811837}
812838
839+ // authRetryDelay is how long to wait before asking a second time about a refused
840+ // password. Short enough to stay inside the restart floor that is being spent
841+ // anyway, long enough to outlast the momentary gap it exists to tolerate.
842+ const authRetryDelay = 2 * time .Second
843+
844+ // connectClient builds the client, and when the server refuses the password asks
845+ // once more before believing it.
846+ //
847+ // AuthError carries no more meaning than "the server answered with something
848+ // other than 233". A server whose auth backend is momentarily unavailable serves
849+ // the request through its masquerade handler instead -- a 404 -- which is the
850+ // same answer a genuinely wrong password gets, and the client cannot tell them
851+ // apart. With lazy off this handshake is a single un-retried attempt, and the
852+ // caller turns an AuthError into a status file that makes the netifd handler
853+ // call proto_block_restart. One unlucky moment would then hold the WAN down
854+ // until somebody logged in and ran ifup, where before it recovered by itself.
855+ //
856+ // Asking twice separates the two: a wrong password is refused both times, a gap
857+ // almost never lasts across the delay. Only an auth failure is retried -- every
858+ // other error is already treated as retryable and is left to netifd.
859+ func connectClient (config clientConfig , connected func (client.Client , * client.HandshakeInfo , int )) (client.Client , error ) {
860+ c , err := client .NewReconnectableClient (config .Config , connected , config .Lazy )
861+ var authErr coreErrs.AuthError
862+ if err == nil || ! errors .As (err , & authErr ) {
863+ return c , err
864+ }
865+ logger .Warn ("server refused the password, confirming before reporting it as final" ,
866+ zap .Int ("status" , authErr .StatusCode ))
867+ time .Sleep (authRetryDelay )
868+ return client .NewReconnectableClient (config .Config , connected , config .Lazy )
869+ }
870+
813871func runClient (v * viper.Viper ) {
814872 if err := v .ReadInConfig (); err != nil {
873+ holdPPPRestart ()
815874 logger .Fatal ("failed to read client config" , zap .Error (err ))
816875 }
817876 var config clientConfig
818877 if err := v .Unmarshal (& config ); err != nil {
878+ holdPPPRestart ()
819879 logger .Fatal ("failed to parse client config" , zap .Error (err ))
820880 }
821881
822- c , err := client .NewReconnectableClient (
823- config .Config ,
824- func (c client.Client , info * client.HandshakeInfo , count int ) {
825- connectLog (info , count )
826- // On the client side, we start checking for updates after we successfully connect
827- // to the server, which, depending on whether lazy mode is enabled, may or may not
828- // be immediately after the client starts. We don't want the update check request
829- // to interfere with the lazy mode option.
830- if count == 1 && ! disableUpdateCheck {
831- go runCheckUpdateClient (c )
832- }
833- }, config .Lazy ,
834- )
882+ connected := func (c client.Client , info * client.HandshakeInfo , count int ) {
883+ connectLog (info , count )
884+ // On the client side, we start checking for updates after we successfully connect
885+ // to the server, which, depending on whether lazy mode is enabled, may or may not
886+ // be immediately after the client starts. We don't want the update check request
887+ // to interfere with the lazy mode option.
888+ if count == 1 && ! disableUpdateCheck {
889+ go runCheckUpdateClient (c )
890+ }
891+ }
892+
893+ c , err := connectClient (config , connected )
835894 if err != nil {
895+ // Two unrelated things share this line. The status write is where a
896+ // refused Hysteria2 password is reported from: with lazy off the
897+ // handshake has already happened here, so it never reaches the runner
898+ // below. The hold is for the opposite case -- a dropped upstream fails
899+ // this dial in microseconds, and without it that becomes a restart loop.
900+ writePPPStatus (err )
901+ // And the state file, which reports every failure rather than only the
902+ // permanent ones. A handshake refused here never reaches PPP mode, so
903+ // without this the link's last published state is whatever it was before
904+ // the interface was rebuilt -- which for a link that was working a moment
905+ // ago is "connected".
906+ writePPPStateDown (err )
907+ holdPPPRestart ()
836908 logger .Fatal ("failed to initialize client" , zap .Error (err ))
837909 }
838910 defer c .Close ()
@@ -887,6 +959,11 @@ func runClient(v *viper.Viper) {
887959 return clientTUN (* config .TUN , c )
888960 })
889961 }
962+ if config .PPP != nil {
963+ runner .Add ("PPP" , func () error {
964+ return clientPPP (* config .PPP , c , strings .EqualFold (config .Obfs .Type , "salamander" ))
965+ })
966+ }
890967
891968 signalChan := make (chan os.Signal , 1 )
892969 signal .Notify (signalChan , os .Interrupt , syscall .SIGTERM )
@@ -904,7 +981,31 @@ func runClient(v *viper.Viper) {
904981 if r .OK {
905982 logger .Info (r .Msg )
906983 } else {
984+ // A connect that fails only reaches here in lazy mode, where the
985+ // handshake is deferred until PPP mode asks for the conn. With lazy
986+ // off -- which is what the OpenWrt handler generates, since it never
987+ // writes the key -- the handshake has already happened above, and a
988+ // refused password fails there instead. Both sites write the status.
989+ writePPPStatus (r .Err )
990+ writePPPStateDown (r .Err )
907991 _ = c .Close () // Close the client here as Fatal will exit the program without running defer
992+ // Hand SIGTERM back to the runtime before holding. The deferred Stop
993+ // never runs -- Fatal exits -- so without this an ifdown arriving
994+ // during the hold would sit unread in signalChan and be ignored for
995+ // the length of it, keeping netifd waiting on a process that has
996+ // already decided to die.
997+ signal .Stop (signalChan )
998+ // Stop only restores the default disposition for signals still to
999+ // come. One that arrived while this select was being decided is
1000+ // already buffered, and the select is free to have picked the runner
1001+ // case instead -- so it has to be looked for, not waited for. Finding
1002+ // one means an ifdown is in progress and nothing is going to restart
1003+ // us, which is exactly when holding is pure delay.
1004+ select {
1005+ case <- signalChan :
1006+ default :
1007+ holdPPPRestart ()
1008+ }
9081009 if r .Err != nil {
9091010 logger .Fatal (r .Msg , zap .Error (r .Err ))
9101011 } else {
0 commit comments